#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)
(undek_corect
este răspunsul corect, iark_program
răspunsul dat de sursa trimisă) - pentru afișarea doar a valorii lui
k
se va acorda doar50%
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); }