Spatrat

#1092

Să se scrie un număr natural n ca sumă de pătrate perfecte. De asemenea, numărul termenilor trebuie să fie minim.

Date de intrare

Fișierul de intrare spatrat.in conține un număr natural n, numărul care se cere să fie scris ca suma de patrate perfecte.

Date de ieșire

Fișierul de ieșire spatrat.out va conține pe prima linie un număr k, reprezentând numărul termenilor din adunare. Pe a doua linie se vor scrie cele k numere care ridicate la pătrat și adunate dau n, separate printr-un spațiu.

Restricții și precizări

  • 1 ≤ n ≤ 100.000
  • pentru 40% dintre teste, n ≤ 1000
  • punctajul pe un test complet se acordă astfel: max(5 - (k_program - k_corect), 0) (unde k_corect este răspunsul corect, iar k_program răspunsul dat de sursa trimisă)
  • pentru afișarea doar a valorii lui k se va acorda doar 50% din punctajul calculat după formula de mai sus pentru fiecare test
  • lăcomia nu se răsplătește cu punctajul maxim!

Exemplu

spatrat.in

18

spatrat.out

2
3 3

Explicație

3 2 + 3 2 = 9 + 9 = 18
18 nu este pătrat perfect, deci nu se poate scrie ca sumă formată din el însuși (ca să existe soluție pentru k = 1)

INDICATII

Greedy (51 de puncte):
Complexitate O(sqrt(n)) timp și O(1) spațiu

Vom lua pe rând toate pătratele perfecte de la n la 1 și pentru pătratul perfect al lui x (x este între 1, n) îl vom adăuga la soluție cât timp este posibil, apoi trecem la pătratul lui x-1 etc.

O soluție euristică nu va scoate punctajul maxim, ceea ce reiese și din exemplu. Aceasta ar presupune următoarea scriere: 18 = 16 + 1 + 1 = 4 2 + 1 2 + 1 2, dar există o soluție optimă când k = 2.

Așadar, soluția care garantează punctajul maxim constă în programare dinamică.

Programare dinamică (100 de puncte):
Complexitate O(n * sqrt(n)) timp și O(n) spațiu

Vom lua un vector d în care memorăm în d[i] numărul minim de termeni necesari pentru a obține numărul i. Astfel, pentru fiecare poziție din vectorul d în care se poate ajunge vom adăuga un pătrat perfect din intervalul 1, n .

Formula de recurență va arăta în felul următor: d[i + j * j] = min(d[i] + 1, d[i + j * j]) unde i este numărul curent care se scrie ca sumă de d[i] pătrate, iar j este un număr din intervalul 1, sqrt(n).

Pentru a afișa la final numerele, vom lua un vector de tați t, în care memorăm în t[i] care este numărul, care ridicat la pătrat, a fost adunat pentru a ajunge la numărul i. Astfel, pentru a afișa toate numerele, ne vom folosi de următorul algoritm:

afișăm t[i];
i ← i - t[i] * t[i];

#include <fstream>
#include <cmath>
using namespace std;

ifstream fin("spatrat.in");
ofstream fout ("spatrat.out");

const int N = 1e5 + 5, oo = 1e9;

int d[N], t[N], n;

int main() {
    fin >> n;
    for (int i = 1; i <= n; ++i)
        d[i] = t[i] = oo;
    for (int j = sqrt(n); j; --j) {
        for (int i = 0; i <= n - j * j; ++i)
            if (d[i] != oo && d[i + j * j] > d[i] + 1) {
                d[i + j * j] = d[i] + 1;
                t[i + j * j] = j;
            }
    }
    fout << d[n] << "\n";
    while (t[n]) {
        fout << t[n] << " ";
        n -= t[n] * t[n];
    }
}
#include <fstream>
//#include<iostream>
#include <algorithm>

#include <cmath>
#define nmax 1001
using namespace std;

ifstream cin("spatrat.in");
ofstream cout("spatrat.out");

int n, k;
int t[nmax];
int dt[nmax * nmax], d[nmax * nmax];

int ap(int n){
    if(d[n] != 0)
        ap(d[n]);

    cout << sqrt(n - d[n]) << " ";
}

int main() {
    cin >> n;

    int p = 1;

    while(p * p <= 100000){
        t[++k] = p * p;
        p++;
    }

    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= k && i - t[j] >= 0; ++j){
            if(dt[i] == 0){
                dt[i] = dt[i - t[j]] + 1;
                d[i] = i - t[j];
            }

            if(dt[i] > dt[i - t[j]] + 1)
                dt[i] = dt[i - t[j]] + 1,
                d[i] = i - t[j];
        }
    }

    cout << dt[n] << '\n';

    ap(n);
}