#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_corecteste răspunsul corect, iark_programrăspunsul dat de sursa trimisă) - pentru afișarea doar a valorii lui
kse 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 = 1818 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);
}