Omówienie zadania Gwiazdy (II etap XXVI OI)

Dane jest \(n\) gwiazd na linii prostej, ponumerowanych od \(1\) do \(n\). Zaczynamy w gwieździe \(s\) i chcemy wykonać \(n-1\) skoków, by odwiedzić wszystkie gwiazdy; \(i\)-ty skok może być w lewo (i płacimy wtedy koszt \(l_i\)) lub w prawo (i płacimy \(r_i\)). Należy znaleźć ciąg odwiedzeń gwiazd minimalizujący sumę kosztów skoków.

Podzadanie 1

Najprostsze rozwiązanie sprawdza wszystkie możliwe permutacje odwiedzonych gwiazd, których jest \((n - 1)!\) i działa w złożoności czasowej \(O(n!)\).

Podzadanie 2

Można też skonstruować programowanie dynamiczne, którego stan definiowany jest przez zbiór odwiedzonych gwiazd oraz obecną gwiazdę, jest ich więc \(O(2^n n)\). Wynik dla konkretnego stanu da się obliczyć w czasie liniowym od liczby gwiazd, rozpatrując zbiór gwiazd pomniejszony o teraz odwiedzoną. Rozwiązanie to działa w złożoności czasowej \(O(2^n n^2)\).

Podzadanie 3

Szybko nasuwa się też rozwiązanie wielomianowe o złożoności \(O(n^3)\). Gwiazdy są nierozróżnialne, jedyne, co ogranicza nasze skoki, to liczba nieodwiedzonych gwiazd po lewej i po prawej stronie aktualnej gwiazdy. Mamy więc \(O(n^2)\) stanów i do każdego z nich możemy dostać się z dowolnego innego stanu o sumie liczb gwiazd po obu stronach większej o jeden, a takich stanów jest \(O(n)\).

Podzadanie 4

Poprzednie rozwiązanie da się jeszcze ulepszyć – załóżmy, że mamy uzupełnione wszystkie stany, w których suma liczb gwiazd po obu stronach jest równa \(i + 1\). Niech stan \((l, p)\) oznacza taki, w którym po lewej mamy \(l\) nieodwiedzonych gwiazd, a po prawej \(p\) oraz \(l + p = i\). Do danego stanu \((l, p)\) możemy dostać się, skacząc w prawo z dowolnego stanu \((l - x, p + x + 1)\), gdzie \(x \leq l\). Analogicznie, możemy się tam dostać, skacząc w lewo z dowolnego stanu \((l + x + 1, p - x)\), gdzie \(x \leq p\). W obu przypadkach interesuje nas stan najlepszy, czyli o najmniejszym koszcie.

Możemy więc skonstruować rozwiązanie, które dla danego poziomu \(i\) uzupełni wszystkie stany na tym poziomie w czasie liniowym. Zastosujemy dwa przejścia, od \((0, i)\) do \((i, 0)\), a później w drugą stronę. W pierwszym będziemy trzymać w każdym kroku najlepszy możliwy stan, z którego mogliśmy skoczyć w prawo do naszego stanu, a w drugim analogicznie dla skoku w lewo. To rozwiązanie działa w czasie \(O(n^2)\).

Podzadanie 5

Ponieważ \(l_i \leq r_i\) dla każdego skoku, więc najchętniej zrobilibyśmy jedynie skoki w lewo. To jest możliwe, jeśli \(s=n\). Dla \(s\neq n\) musimy zrobić jakiś skok w prawo (jako jeden z \(s\) pierwszych skoków), ale jeden skok w prawo wystarczy. Zatem wybieramy taki, który minimalizuje \(r_i - l_i\) dla \(i \leq s\).

Czas działania to \(O(n)\).

Podzadanie 6

Skoro koszt całkowity wynosi 0 i dla każdego skoku albo \(l_i=0\), albo \(r_i=0\), to dokładnie wiemy, w których kierunkach musimy skakać (zawsze skokiem o koszcie 0).

Dzielimy skoki na bloki o tych samych kierunkach. Będziemy zachowywać niezmiennik, że zbiór nieodwiedzonych gwiazd stanowi spójny przedział (pomijamy przy tym początkową gwiazdę \(s\)). Jeśli mamy wykonać \(a\) skoków w lewo, to po lewo od aktualnej gwiazdy musi być co najmniej \(a\) gwiazd. Skaczemy po kolei do \(a\)-tej, \((a-1)\)-szej, …, drugiej, pierwszej nieodwiedzonej gwiazdy. Jeśli mamy wykonać \(b\) skoków w prawo, to odwiedzamy po kolei gwiazdy z sufiksu długości \(b\).

Złożoność czasowa również \(O(n)\).

Podzadanie 7

Jeśli \(s=1\), to pierwszy skok będziemy musieli wykonać w prawo. Ale później będziemy już mogli zawsze wybierać skok w kierunku mniejszego kosztu (aby zapłacić \(\min(l_i,r_i)\)), metodą jak w poprzednim podzadaniu. Złożoność czasowa \(O(n)\).

Pełne rozwiązanie

Dzielimy skoki na bloki o tych samych kierunkach, wybierając zawsze skok do mniejszego kosztu. Dla uproszczenia analizy załóżmy, że pierwszy blok skoków jest w lewo. Jeśli jego długość jest mniejsza niż \(s\), to wykonujemy wszystkie skoki jak w podzadaniu 6. W przeciwnym wypadku jeden z \(s\) pierwszych skoków musimy zamienić na skok w prawo (jak w podzadaniu 5 – wybieramy ten minimalizujący \(r_i - l_i\) dla \(i \leq s\)).

Przypadek pierwszego bloku skoków w prawo jest symetryczny. Czas działania to \(O(n)\).

 


Opis przygotowano w ramach projektu "Mistrzostwa w Algorytmice i Programowaniu - Uczniowie" - zadania finansowanego ze środków Ministerstwa Cyfryzacji.