Informatyczny kącik olimpijski
Drogi Stanowe
Tym razem omówimy zadanie o drogach stanowych (State Roads) z pierwszej rundy zawodów Yandex.Algorithm 2013. Zadanie to można sformułować w języku teorii grafów, a do tego w ujęciu dynamicznym...
Zadanie. Mamy więc graf nieskierowany o zbiorze wierzchołków
początkowo pusty, w którym w kolejnych
momentach pojawiają się i znikają krawędzie. Pomiędzy takimi zdarzeniami
otrzymujemy zapytania, z których każde dotyczy pewnego podzbioru
wierzchołków grafu
W odpowiedzi na takie zapytanie mamy
stwierdzić, czy z jakiegokolwiek wierzchołka zbioru
wychodzi
jakaś krawędź poza ten zbiór; innymi słowy, czy wierzchołki zbioru
w rozważanym momencie stanowią sumę pewnej liczby spójnych
składowych grafu. W zadaniu występuje też techniczny (ale przyjemny)
warunek, że wstawiane krawędzie otrzymują numery będące kolejnymi
liczbami naturalnymi i za pomocą tych numerów oznaczane są krawędzie
przeznaczone do usunięcia.
Zastanówmy się najpierw nad rozwiązaniem siłowym. Tym razem, celowo,
omówimy dość dokładnie nietrywialne szczegóły implementacyjne
rozwiązania. Cały graf możemy przechowywać po prostu jako
tablicę list sąsiedztwa. Przy wstawianiu krawędzi
o numerze
dodajemy po jednym nowym elemencie do list sąsiedztwa
wierzchołków
i
i w osobnej tablicy indeksowanej
numerem krawędzi zapamiętujemy wskaźniki na elementy list, które
dodaliśmy dla tej krawędzi. Takie wstawienie działa oczywiście w czasie
stałym:

Dzięki wspomnianemu przyjemnemu warunkowi technicznemu uprzednio wstawioną krawędź możemy usunąć z grafu również w czasie stałym, po prostu usuwając odpowiednie elementy list sąsiedztwa:

Bardziej czasochłonną operacją jest, oczywiście, odpowiedź na zapytanie. Wczytując wierzchołki występujące w zapytaniu, możemy równocześnie zaznaczać wszystkie te wierzchołki w pomocniczej tablicy indeksowanej numerami wierzchołków. Żeby nie musieć za każdym razem zerować tej tablicy, wierzchołki z każdego nowego zapytania możemy oznaczać numerem porządkowym tego zapytania. Teraz obsługa zapytania jest już bardzo prosta – przeglądamy kolejne wierzchołki z zapytania, dla każdego z nich przeglądamy wszystkie incydentne krawędzie i sprawdzamy, czy drugie końce tych krawędzi są zaznaczone w tablicy pomocniczej:

Na pierwszy rzut oka widać, że obsługa zapytania jest tutaj bardzo wolna.
Warto jednak przyjrzeć się temu dokładniej. Przede wszystkim należy
ustalić, co jest dla nas rozmiarem danych wejściowych. Oznaczmy przez
liczbę wierzchołków grafu, a przez
– łączną liczbę krawędzi
wstawionych kiedykolwiek do grafu. Niech
(dla
)
oznacza liczbę wierzchołków występujących w
-tym zapytaniu,
Całkiem naturalnie jako rozmiar danych wejściowych
przyjmiemy
Jak teraz wyrazić względem
koszt obsługi wszystkich zapytań?
Zauważmy, że w
-tym zapytaniu będziemy musieli przejrzeć co
najwyżej
krawędzi. Faktycznie, tyle krawędzi (liczonych
w obie strony) ma klika o
wierzchołkach, więc jeśli w listach
sąsiedztwa wierzchołków z zapytania znajdzie się więcej niż tyle krawędzi, to
na pewno odpowiedź na to zapytanie będzie negatywna. Asymptotycznie daje to
koszt
Z drugiej strony, w każdym zapytaniu przejrzymy
łącznie co najwyżej
krawędzi. Ostatecznie koszt zapytania to
Skorzystajmy teraz z nierówności między minimum
a średnią geometryczną:

Całkowity koszt obsługi wszystkich zapytań możemy oszacować przez:

Czyli jednak to rozwiązanie nie było znowuż aż takie wolne!
Jednak da się lepiej. I to dużo lepiej, bowiem liniowo względem rozmiaru
danych wejściowych. Rozwiązanie opiera się na następującym pomyśle:
obliczmy sumę stopni wierzchołków ze zbioru
Jeśli suma ta
jest nieparzysta, to na pewno z
wychodzi jakaś krawędź na
zewnątrz, bowiem w każdym grafie suma stopni wierzchołków jest
parzysta. Pomysł ten w ogólności nie działa – może się okazać, że
suma stopni wierzchołków w
będzie parzysta, a mimo tego
odpowiedź na zapytanie będzie negatywna. Uogólnijmy zatem nasz pomysł
i wprowadźmy element randomizacji. Każdej krawędzi wstawianej do grafu
przypiszmy losową wagę będącą liczbą całkowitą. Dla każdego wierzchołka
będziemy przechowywać (jako
) bitową alternatywę
wykluczającą wag krawędzi incydentnych z tym wierzchołkiem. Przy
wstawianiu i usuwaniu krawędzi tablicę
możemy aktualizować
w czasie stałym – w obu przypadkach wystarczy do-xor-ować wagę
krawędzi do
-ów jej końców. W przypadku wstawiania będzie
to:

Kluczowa obserwacja jest teraz taka: odpowiedź na zapytanie
jest pozytywna wtedy i tylko wtedy, gdy każda
krawędź w grafie jest incydentna z dwoma wierzchołkami ze zbioru
lub z żadnym z nich. Ponieważ
zatem aby
odpowiedzieć na zapytanie, obliczamy wartość:

Jeśli powyższa wartość jest różna od zera, na pewno odpowiedź na
zapytanie jest negatywna. W przeciwnym razie z dużym prawdopodobieństwem
odpowiedź jest pozytywna. Prawdopodobieństwo udzielenia błędnej
odpowiedzi, czyli trafienia przypadkiem na 0, wynosi
gdzie
jest ograniczeniem górnym na wagę krawędzi. Przyjmując, naturalnie,
lub
otrzymujemy rzeczywiście bardzo małe
prawdopodobieństwo błędu.