https://spoj.com/ALGOLIGA/problems/AL_17_04
https://pl.spoj.com/problems/AL_17_04
Skrócony opis problemu:
Mając n liczb, które sumują się do x należy sprawdzić czy da się wybrać takie, żeby zsumowały się do \frac{x}{2}.
Rozwiązanie naiwne:
Naiwne rozwiązanie to sprawdzenie wszystkich możliwości. W tym przypadku byłoby to sprawdzenie dla każdego podzbioru liczb czy sumuje się do \frac{x}{2}.
Złożoność: O\left(2^n \cdot n\right), gdyż musielibyśmy sprawdzić wszystkie podzbiory o wielkościach od 1 do n-1, a jest ich 2^{n-1}. Musielibyśmy także obliczyć sumę każdego z nich, co dałoby nam pesymistycznie n-1 operacji.
Rozwiązanie sprytne:
Możemy skorzystać z programowania dynamicznego. Zróbmy sobie zatem tablicę dp o wielkości równej \frac{x}{2}+1. Dla i-tego elementu będziemy trzymali w nim 1, jeśli udało się nam go otrzymać z sumowania części z dotychczasowych liczb lub 0, w przeciwnym wypadku. Na początku więc wszystkie komórki mają 0, oprócz tej o indeksie 0, gdyż 0 możemy otrzymać nie sumując żadnych liczb. A zatem na weźmy na początku jedną liczbę - 7 - i ustawmy dp[7] = 1. Powiedzmy, że kolejna liczba to 3. Nie wystarczy jednak ustawić dp[3] = 1, gdyż możemy od tego momentu utworzyć również 7+3=10. Musimy więc przechodzić tablicę od końca i dla każdej komórki i, której wartość wynosi 1 musimy ustawiać dp[i+3] = 1, chyba że i+3 przekroczy \frac{x}{2} - sumy większe od połowy x nas bowiem nie obchodzą. A dlaczego mamy przechodzić od końca ową tablicę? Ano dlatego, że gdybyśmy przechodzili ją od początku to bralibyśmy daną liczbę pod uwagę kilka razy zamiast jednego razu. Np. najpierw zaktualizowalibyśmy dp[0+3] = 1, a następnie dla komórki o indeksie 3 zrobilibyśmy to samo, jako że jej wartość jest równa 1. Idąc od końca zmieniamy wartości tylko tych komórek, które już przeszliśmy i do których nie wrócimy.
Powtórzę jeszcze raz co nam daje owa tablica. Komórka o indeksie i pokazuje nam czy da się stworzyć liczbę i sumując przetworzone do tej pory liczby. Chcemy przetworzyć następną liczbę - y. Idziemy więc od końca tablicy i sprawdzamy czy któraś komórka ma wartość 1. Jeśli tak (przyjmijmy, że stanęliśmy na komórce j), to znaczy, że możemy uzyskać j, a więc możemy również uzyskać y+j. Oznaczamy więc dp[y+j] = 1 i idziemy dalej. Na końcu dojdziemy do 0 i robimy dp[0+y] = 1, gdyż możemy uzyskać także y nie sumując jej z żadną liczbą. Pracę kończymy gdy przetworzymy wszystkie liczby z wejścia lub gdy osiągniemy \frac{x}{2} (a więc gdy dp\left[\frac{x}{2}\right] = 1).
Optymalizacje:
Żeby nie komplikować, nie wspomniałem, że na początku wejścia jest jeszcze liczba t oznaczająca liczbę przypadków w danym teście. Tak więc dla każdego testu trzeba by zerować tablicę dp. Można jednak zastosować sztuczkę, by nie tracić czasu na zerowanie tej tablicy. Wystarczy każdemu testowi przypisać kolejną liczbę naturalną: 1, 2, ... t. Będzie ona zastępowała nam w danym teście jedynkę, którą oznaczaliśmy czy daną liczbę da się otrzymać sumując część liczb z wejścia. Tak więc na początku testu nr 3 oznaczymy dp[0] = 3, a pozostałe, mniejsze od 3 wartości będziemy traktować jako zera. Tak więc dla testu nr 5 uda nam się otrzymać \frac{x}{2} jeśli po przejściu wszystkich liczb dp\left[\frac{x}{2}\right] = 5.
Kolejnymi optymalizacjami jest wypisywanie od razu NIE jeśli suma wszystkich liczb jest nieparzysta lub gdy największa liczba przekracza połowę sumy wszystkich liczb.
Złożoność: O\left(n \cdot \frac{x}{2}\right), gdyż dla każdej z n liczb przechodzimy dokładnie \frac{x}{2} komórek. Ograniczenie na x w zadaniu to 10^5, a na n to 1000, więc możemy oszacować złożoność jako O\left(n^2 \sqrt n\right).
Algorytm:
int main() { int t, n, i, j, sum, partsum, dp[50009], in[1009]; get(t); while(t--) { get(n); partsum = sum = 0; dp[0] = t+1; for(i = 0; i < n; ++i) get(in[i]), sum += in[i]; if(sum&1) // jeśli suma jest nieparzysta, to oczywiście od razu wypisujemy NIE { puts("NIE"); continue; } sum >>= 1; // dzielimy sumę przez 2, żeby nie pisać ciągle sum/2 for(i = 0; i < n; ++i) { for(j = partsum > sum ? sum : partsum; j >= 0; --j) // dla każdej liczby od końca tablicy lub sumy wszystkich dotychczasowych liczb (jeśli ta suma jest mniejsza od końca tablicy) if(dp[j] == t+1 && j+in[i] <= sum) // jeśli daną liczbę j da się utworzyć i jeśli powiększając tę liczbę o in[i] nie wyjdziemy poza tablicę dp[j+in[i]] = t+1; // oznaczamy, że możemy utworzyć również j+in[i] if(dp[sum] == t+1) // jeśli da się utworzyć x/2 to kończymy, bo po co sprawdzać dalsze liczby break; partsum += in[i]; // zwiększamy dotychczasową sumę } puts(dp[sum] == t+1 ? "TAK" : "NIE"); } }
Brak komentarzy:
Prześlij komentarz