Informatyczny kącik olimpijski
Godzilla
W tym miesiącu omówimy zadanie, które rozwiązywali uczestnicy Obozu Naukowo-Treningowego im. A. Kreczmara w 2009 r.

Zadanie 1. Sieć telewizji kablowej składa się z
węzłów
i
jednokierunkowych połączeń. Do każdego węzła sieci jest
podłączony co najmniej jeden dom. Niektóre węzły są wyróżnione
i do nich bezpośrednio transmitowany jest program. W danym domu
można go oglądać, jeśli istnieje połączenie (niekoniecznie bezpośrednie)
od wyróżnionego węzła do węzła, do którego podłączony jest dom.
Niestety, sieć jest narażona na ataki złośliwej Godzilli, która,
nie wiedzieć czemu, żywi się infrastrukturą telewizji kablowej. Co dzień
zjada ona jedno połączenie z sieci. Mając daną listę
połączeń,
które kolejno zje Godzilla, należy wyznaczyć, ile minimalnie węzłów
należy oznaczyć jako wyróżnione każdego dnia, tak aby każdy dom
mógł odbierać program.
Sieć kablową możemy traktować jako graf skierowany
o
wierzchołkach i
krawędziach. Aby znaleźć minimalny
zbiór wyróżnionych węzłów, wyznaczamy podział grafu
na
silnie spójne składowe. Silnie spójną składową nazwiemy początkową, jeśli
nie wchodzi do niej żadna krawędź (tzn. do żadnego wierzchołka tej
składowej nie wchodzi krawędź z wierzchołka spoza tej składowej).
Zauważmy, że w każdej początkowej składowej musimy wyróżnić
co najmniej jeden wierzchołek, aby dostarczyć program do wierzchołków tej
składowej. A ponieważ do każdej niepoczątkowej składowej istnieje
ścieżka ze składowej początkowej, więc taki zbiór wierzchołków
jest wystarczający.
Znajdowanie podziału na silnie spójne składowe możemy zrealizować
dwukrotnym przejściem grafu w głąb w czasie
Jeśli
po każdym usunięciu krawędzi będziemy wyznaczali ten podział od nowa,
dostaniemy algorytm o koszcie czasowym
Chcielibyśmy umieć szybko uaktualniać strukturę składowych po usunięciu
krawędzi. Nie jest jednak jasne, jak to zrobić, gdyż po takiej operacji jedna
ze składowych może rozpaść się na mniejsze składowe i wydaje się, że
nie da się ich wyznaczyć szybciej niż w czasie liniowym względem rozmiaru
oryginalnej składowej. Zastosujemy więc pomysłową sztuczkę i odwrócimy
czas: zaczniemy od grafu, w którym usunięto wszystkie
krawędzi,
i będziemy je dodawać do grafu w odwrotnej kolejności. Tym sposobem
pojedynczą operacją będzie połączenie kilku składowych w jedną, a to już
wygląda przyjaźniej.
Niech
będzie aktualną liczbą początkowych składowych. Przez cały czas
będziemy utrzymywać podział zbioru wierzchołków grafu

Zbiór
tworzą wierzchołki
-tej początkowej składowej; do
zbioru
należą zaś wierzchołki, które są osiągalne z
(jeśli
wierzchołek jest osiągalny z kilku początkowych składowych, to należy do
zbioru
dla jednej z tych składowych).
Zobaczmy, co się dzieje, gdy dodajemy do grafu nową krawędź
Jeśli
dla pewnego
to dodanie tej krawędzi nie spowoduje
żadnych zmian w strukturze początkowych składowych, a więc nie musimy
nic robić.
W przeciwnym przypadku
dla pewnego
Będziemy
przeszukiwać graf transponowany
(czyli przechodzić krawędzie
grafu
w odwrotnym kierunku), np. algorytmem DFS, począwszy
od wierzchołka
Dla każdego napotkanego wierzchołka
ze zbioru
przenosimy go do zbioru
(wiemy, że
jest cykl
gdyż
jest osiągalny z
).
Oczywiście, dla każdego napotkanego wierzchołka
ucinamy
przeszukiwanie, gdyż gdy raz wejdziemy do
to już z niego
nie wyjdziemy.
Jeśli zaś napotkamy wierzchołek
dla
to znaczy,
że znaleźliśmy ścieżkę, która pokazuje, iż składowa
stała się
osiągalna ze składowej
Zatem powiększamy
wykonując
usuwamy zbiory
i kończymy
przeszukiwanie. Liczba początkowych składowych zmalała o jeden.
Oszacujmy złożoność czasową naszego algorytmu. Obliczenie podziału dla
początkowego grafu
bez
zjedzonych krawędzi realizujemy
w czasie
Podział ten będziemy przechowywać
w strukturze danych dla zbiorów rozłącznych (ang. find-union). Wtedy
każda operacja 1) zapytania, do którego ze zbiorów należy dany
wierzchołek i 2) złączenia zbiorów będzie miała „prawie stały” koszt
zamortyzowany – a dokładniej
przy czym w praktyce
Dodatkowo, sumaryczny czas przeszukiwania grafu
wynosi
gdyż każdą krawędzią przechodzimy
co najwyżej raz.
Zatem całkowity koszt czasowy to
przy zużyciu
pamięci rzędu