Processing math: 0%

środa, 12 listopada 2014

20684. Czary Tadeusza [CZRTAD]

Zadanie:
https://pl.spoj.com/problems/CZRTAD

Skrócony opis problemu:
Dla danego ciągu liczb a o długości n wyznacz najmniejszą taką liczbę x, że z podanego ciągu można zrobić ciąg rosnący odejmując lub dodając do każdego wyrazu ciągu maksymalnie x.

Rozwiązanie:
Wyjściem w zadaniu jest 1 liczba. Musimy ją zminimalizować.W takich zadaniach sprawdza się wyszukiwanie binarne po wyniku.

A więc na początku ustawiamy sobie przedział, w którym będziemy szukać naszego wyniku. Maksymalne wartości wyrazów ciągu wynoszą w zadaniu 10^9, więc przyjmiemy początek przedziału jako l = 0, a koniec właśnie jako r = 10^9. Następnie obliczamy środek przedziału m i sprawdzamy czy da się dla niego osiągnąć ciąg rosnący odejmując i dodając maksymalnie m do elementów ciągu. Jeśli się da, to znaczy, że możemy ustawić r = m, bo interesuje nas najmniejsza wartość, dla której się da (więc skoro dla m się da, to ignorujemy wartości większe od m). Jeśli jednak się nie da, to ustawiamy l = m+1, bo nie interesują nas wartości, dla których się nie da (a więc l też nie). Powtarzamy te operacje aż l = r.

Ale żeby ów algorytm był wystarczająco efektywny, musielibyśmy umieć szybko sprawdzić czy dla danego m ciąg może być modyfikowany na rosnący. No i okazuje się, że da się to zrobić liniowo, czyli przechodząc każdy element tylko raz. Gdy chcemy jedynie sprawdzić czy możemy stworzyć ciąg rosnący dla konkretnej (nie niewiadomej) liczby, to możemy wykorzystać myślenie zachłanne.

A zatem, idąc od lewej, minimalizujemy jak się da wartość kolejnych wyrazów ciągu. Tak więc pierwszy element od razu pomniejszamy o m i ustawiamy zmienną last = a_0-m (numerując wyrazy od 0). Od kolejnego też odejmujemy m? Ano niekoniecznie. Może się bowiem okazać, że po zmniejszeniu a_i będzie on mniejszy lub równy poprzedniemu (czyli zmiennej last). To jednak nie problem - jeśli dany element jest większy od last, ale po odjęciu od niego m jest mniejszy lub równy last to po prostu ustawiamy jego wartość na najmniejszą możliwą, czyli last + 1. A więc robimy wtedy last++. Możliwe jest jednak, że dany element a_i będzie większy od last. Co możemy wtedy zrobić? Ano możemy przecież do niego dodać m. A zatem, sprawdzamy ile będzie wynosiło a_i + m. Jeśli będzie większe od a_{i-1}, to po prostu zwiększamy last o 1, jak poprzednio - chcemy bowiem mieć jak najmniejszy element last, więc dodamy tylko tyle do a_i, żeby przekroczył last. Jeśli jednak a_i + m będzie nadal mniejsze lub równe last, to znaczy, że nie da rady zmodyfikować ciągu a, żeby był rosnący dla tak małego m.

A zatem, podsumowując:
  • od pierwszego elementu odejmujemy m
  • jeżeli po odjęciu od a_i liczby m będzie ona większa od last to odejmujemy właśnie tyle, bo jest to najwięcej na ile możemy sobie pozwolić
  • jeżeli po odjęciu od a_i liczby m będzie ona mniejsza lub równa last to znaczy, że za dużo odjęliśmy, więc ustawiamy last = last+1
  • jeżeli a_i \le a_{i-1} i po dodaniu m otrzymamy liczbę większą niż a_{i-1} to ustawiamy znów last = last+1
  • jeżeli a_i \le a_{i-1} i po dodaniu m liczba ta będzie nadal mniejsza lub równa a_{i-1} to kończymy pętlę z rezultatem negatywnym - nie udało się uzyskać ciągu rosnącego dla tak małego m

Złożoność: O\left(n \cdot \log_2 x\right), bo dla każdego m wykonujemy pesymistycznie około n operacji (jeśli nie przerwiemy pętli). A takich m będzie log_2 x, bo na początku mamy przedział o długości x, potem \frac{x}{2}, potem \frac{x}{4}, itd. Tak więc nasza złożoność zależy od maksymalnego wyniku, jaki możemy osiągnąć. Przyjąłem, że jest to 10^9.

Algorytm:
int main()
{
        int n, l = 1, r = 1000000000, m, ok, last, tab[10000009];

        get(n, tab);
        while(l < r)
        {
                m = l+r>>1; ok = 1; last = *tab-m;
                for(int i = 1; i < n; ++i) //
                {
                        if(tab[i]-m > last)
                                last = tab[i]-m;
                        else if(tab[i] > last || tab[i]+m > last)
                                ++last;
                        else
                        {
                                ok = 0;
                                break;
                        }
                }
                if(ok)
                        r = m;
                else
                        l = m+1;
        }
        print(a);
}

Optymalizacja:
A co jeśli maksymalny wynik z testów jest mniejszy niż miliard? Moglibyśmy wtedy zmniejszyć x w logarytmie. Ale jak to sprawdzić? Ano znów wyszukiwaniem binarnym. Spróbujmy zgłosić program, w którym r = 5 \cdot 10^8, czyli równy połowie poprzedniego przedziału. Jeśli dostaniemy AC to znaczy, że możemy dalej zmniejszać r. Jeśli WA, to znaczy, że musimy go zwiększyć. Tym sposobem dojdziemy do minimalnej wartości r, która da nam AC.

Brak komentarzy:

Prześlij komentarz