Codeforces 1809F
link do zadania: https://codeforces.com/contest/1809/problem/F
Treść
miast (oznaczonych ) znajduje się na pewnym okręgu w tej kolejności. Z miasta możemy przedostać się do -wszego (gdzie dla jako kolejne przyjmujemy miasto ).
Niech oznacza minimalny koszt podróży wokół okręgu, jeśli zaczynamy w mieście . Dla każdego chcemy znaleźć wartość .
Poruszanie się wymaga paliwa, a nasz pojazd ma -litrowy zbiornik (więcej nie zmieścimy w danej jednostce czasu). W mieście koszt paliwa wynosi (zauważ, że przyjmuje bardzo specyficzne wartości). Wiemy też, że odległość z miasta do kolejnego wynosi .
Limity
W liczbach całkowitych:
- (liczba przypadków testowych)
- (liczba miast)
- (maksymalna pojemność zbiornika)
- (odległość do następnego miasta)
- (koszt paliwa w mieście)
Rozwiązanie
Pierwszym typowym w takich zadaniach pomysłem jest “rozwinięcie” okrągu w prostą. Tworzymy dwie kopie każdego miasta, czyli oraz odpowiadają temu samemu miastu. Wtedy każda podróż po okręgu może być reprezentowana jako przedział .
Brute force
Symulujemy proces podróży dla każdego . Możemy zastosować podobny trick jak w zadaniu CF 1238G Adilbek and the Watering System. W każdym momencie czasu trzymamy najlepsze litrów paliwa, które mogliśmy kupić. Będziemy dodawać nowe i wyrzucać to najdroższe. Przy przejściu z miasta do kolejnego dokonujemy zakupu najtańszych paliwa, które zapamiętaliśmy.
Warto dodać, że w takim rozwiązaniu możemy pominąć założenie o . Możemy użyć mapy (potrzebujemy oraz , można też użyć dwustronnej kolejki priorytetowej), wtedy dla każdego początku złożoność czasowa wyniesie . Zatem ostatecznie mamy .
Brute force, ale sprytniejszy
Spróbujmy wykorzystać dodatkowe założenie, które ominęliśmy w poprzednim podejściu.
Po pierwsze, zauważmy, że . Zatem możemy od razu dodać taką wartość do wyniku, a koszty zmniejszyć o , uzyskując odpowiednio . Czyli teraz płacimy tylko w miastach o pierwotnym koszcie (dalej nazywanych typem ), a w pozostałych (typ ) paliwo dostajemy za darmo.
Kiedy coś jest darmowe, oczywistym jest, że chcemy to wykorzystać. W miastach typu będziemy brać maksymalnie dużo paliwa. W miastach typu weźmiemy zupełne minimum - tylko tyle, aby dojechać do następnego. (Nie chcemy przecież sytuacji, gdzie zostało nam jakieś paliwo, za które musieliśmy zapłacić, gdy w nowym mieście możemy je mieć za darmo.)
Stąd pomysł, żeby dla każdego miasta wyznaczyć ostatnie miasto typu , które pojawia się przed nim. W nim kupimy minimum z tego, ile będziemy potrzebować na dalszą podróż i (wliczyliśmy to do wyniku na samym początku). Teraz “łańcuch” ( miasto typu , miasto typu ) miast będzie zużywał to paliwo, w pewnym momencie się ono wyczerpie (lub będzie go niewystarczająco) i dokonamy normalnego zakupu.
Wszystko możemy zrobić dla pojedynczego w , powtarzamy razy, stąd .
Wzorcówka
Zamiast brutalnie symulować proces dla każdego miasta, zauważmy, że duży fragment podróży jest wspólny dla początków w oraz w .
Zatem spróbujmy wykorzystać zwykłe policzenie wyniku dla i “indukcyjnie” zamieniać go w wynik dla w czasie .
Intuicyjne jest utrzymywanie przedziału i przesuwanie obu końców o w prawo. Takie przesunięcie może utworzyć nowy łańcuch lub przeciąć stary.
Ogólnie, miasto ma dwa różne możliwe koszty: (z wpływem poprzedzającego go miasta typu ) oraz (bez tego wpływu).
Zauważmy, że dodanie miasta na koniec (przedłużanie istniejącego łańcucha lub tworzenie nowego) jest proste. Wystarczy sprawdzić, czy początek łańcucha znajduje się w obecnym przedziale. Pozostaje rozwiązać problem aktualizowania łańcucha, który przecinamy poprzez przesunięcie .
Ale i to jest proste i szybkie, ponieważ sumarycznie miast w łańcuchach jest . Możemy robić “two-pointers”, gdzie zamieniamy wyniki miast z na na prefiksie utrzymywanego przedziału.
Zatem końcowa złożoność to amortyzowane .
moje rozwiązanie: https://codeforces.com/contest/1809/submission/356610808
Alternatywne rozwiązanie (An Son Nguyen)
Spróbujmy w inny sposób zoptymalizować brutalne rozwiązanie. Weźmy dowolny indeks o najmniejszym i ustawmy go jako “punkt startowy”. Zauważmy, że każdą optymalną podróż od indeksu po całym okręgu możemy robić na 2 segmenty: od indeksu z pustym zbiornikiem do punktu startowego z pustym zbiornikiem, oraz od punktu startowego z pustym zbiornikiem do z pustym zbiornikiem. Jest tak, gdyż zawsze będzie nam się opłacało kupować jak najwięcej litrów w miejscach o minimalnym . Koszt przejścia drugiego segmentu dla każdego indeksu będziemy obliczać wcześniej omawianym algorytmem zachłannym, zaczynając od punktu startowego i poruszając się zgodnie ze skierowaniem krawędzi. Koszt pierwszych segmentów będziemy zaś liczyć “od końca”, poruszając się od punktu startowego w kierunku przeciwnym do skierowania krawdędzi. Wystarczy nam do tego proste programowanie dynamiczne z gąsienicą.
Ponownie, końcowa złożoność to amortyzowane .
kod Ana Sona: https://codeforces.com/contest/1809/submission/273910141
Refleksja nad zadaniem
Warto zastanowić się, jak rozwiązać zadanie przy innych ograniczeniach, przykładowo dla i dowolnych . Również ciekawa jest mocniejsza wersja oryginalnego zadania dla dowolnych .