Informatyczny kącik olimpijski
Wiercenia
W tym kąciku omówimy zadanie Wiercenia, które pojawiło się na Potyczkach Algorytmicznych w roku 2009.
Ekipa poszukująca złóż ropy wykonała dwa odwierty: w punkcie natrafiono na ropę, zaś w punkcie ropy nie znaleziono. Wiedząc, że złoże ropy zajmuje pewien spójny fragment odcinka ekipa chce zlokalizować najdalszy punkt spośród punktów o współrzędnych w którym występuje ropa. Wykonanie odwiertu w punkcie zajmuje czas ; ekipa może wykonywać tylko jeden odwiert naraz. Należy wyznaczyć taki plan wierceń, aby czas potrzebny na ustalenie, dokąd sięga złoże ropy, był w pesymistycznym przypadku jak najkrótszy.
Na początek zauważmy, że jeśli wykonanie każdego z odwiertów zajmuje taki sam czas, to wtedy, stosując wyszukiwanie binarne, uzyskamy odpowiedź, wykonując optymalną liczbę odwiertów. Zróżnicowanie czasów istotnie komplikuje zadanie; spróbujemy je rozwiązać, korzystając z programowania dynamicznego.
Oznaczmy przez optymalny czas lokalizacji granicy złoża na odcinku od punktu do punktu (włącznie), przy założeniu, że wiemy, iż w punkcie jest ropa, a w punkcie jej nie ma. Aby wyznaczyć granicę złoża na odcinku musimy zacząć od wykonania odwiertu w jednym z punktów tego odcinka. Jeśli znajduje się tam ropa, to granicy złoża będziemy poszukiwać na odcinku w przeciwnym przypadku granica jest na odcinku Dostajemy zatem wzór rekurencyjny
(*) |
Zakładamy, że dla Rozwiązaniem jest wartość Obliczenie jej wprost z powyższej rekurencji da nam algorytm o złożoności czasowej i pamięciowej Algorytm będzie miał faz, w -tej fazie będziemy wypełniać komórki tablicy odpowiadające odcinkom o długości :
Aby przyspieszyć powyższy algorytm, będziemy potrzebowali kilku obserwacji. Zauważmy, że jeśli przedłużymy jakiś odcinek, to czas szukania w nim granicy złoża jest zawsze nie mniejszy niż w oryginalnym odcinku. Zatem wraz ze wzrostem we wzorze wartość nie zmniejsza się, a wartość nie rośnie. Istnieje więc taki indeks że spełnione są nierówności
Możemy zatem przepisać wzór w równoważnej formie, pozbywając się liczenia maksimum:
(**) |
Skorzystamy również z tego, że tablica jest monotoniczna względem każdej współrzędnej, dzięki czemu wartość jest ograniczona przez wartości dla krótszych odcinków:
Istotnie, jeśli weźmiemy dowolne i znowu skorzystamy z tego, że przedłużenie odcinka nie zmniejsza czasu poszukiwania granicy złoża, to dostaniemy zatem co dowodzi lewej nierówności (prawej dowodzimy analogicznie). Ponieważ to możemy dla wygody przyjąć dla Zatem, aby wyznaczyć wystarczy przejrzeć wartości od do Dzięki temu wyznaczenie ich dla wszystkich odcinków o ustalonej długości zajmie sumarycznie czas liniowy:
W końcu pokażemy, jak szybko wyliczać minima we wzorze Załóżmy, że mamy strukturę danych, która przechowuje pary (indeks, wartość) i udostępnia operacje jak na kolejce: wstawienie pary na koniec kolejki (push) oraz usunięcie pary z początku kolejki (pop). Dodatkowo operacja first będzie zwracać indeks pary z początku kolejki, a kluczowa operacja min będzie zwracała minimum ze wszystkich wartości w kolejce. Będziemy korzystać z kolejek, które nazwiemy oraz dla
Podczas wyliczania w -tej fazie algorytmu będziemy zakładać, że wszystkie wartości z lewego minimum w znajdują się w kolejce (w kolejności malejących indeksów ), a wszystkie wartości z prawego minimum w kolejce (w kolejności rosnących indeksów). Kolejka w fazie była wykorzystywana podczas obliczeń dla komórki zatem zawiera pary dla indeksów Ponieważ więc wystarczy usunąć z niej pary o indeksach nie większych niż oraz dodać parę o indeksie Analogicznie uaktualniamy pary w kolejce Pseudokod całego algorytmu jest następujący (zakładamy, że dla pustej kolejki pętla while nie wykonuje się, a operacja min zwraca ):
Kolejki możemy zaimplementować tak, aby wszystkie udostępniane operacje działały w zamortyzowanym czasie stałym. Zauważmy, że tuż przed wykonaniem operacji możemy usunąć z końca kolejki wszystkie pary o wartościach nie mniejszych niż gdyż para będzie lepszym kandydatem na minimum. Dzięki temu pary znajdujące się w kolejce będą zawsze posortowane rosnąco według wartości, zatem operacja po prostu zwróci wartość pary z początku kolejki. Ostatecznie złożoność czasowa algorytmu wyniesie