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
![d[a,i−1] ⩽d[i+1,b] dla i ⩽s[a,b] oraz d[a,i− 1] > d[i+1,b] dla i > s[a, b].](/math/temat/informatyka/algorytmy/2015/05/24/Wiercenia/25x-e61fd52f612df379634bb6066e865c487a205dc1-dm-33,33,33-FF,FF,FF.gif)
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:
![s[a,b − 1]⩽ s[a, b]⩽ s[a +1,b].](/math/temat/informatyka/algorytmy/2015/05/24/Wiercenia/30x-e61fd52f612df379634bb6066e865c487a205dc1-dm-33,33,33-FF,FF,FF.gif)
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:
![n−ℓ (s[a +1,a + ℓ]+ 1− s[a, a+ ℓ −1]) = s[n − ℓ+ 1,n]− s[1,ℓ] +n − ℓ ⩽2n. Qa 1](/math/temat/informatyka/algorytmy/2015/05/24/Wiercenia/42x-e61fd52f612df379634bb6066e865c487a205dc1-dm-33,33,33-FF,FF,FF.gif)
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