Przeskocz do treści

Delta mi!

Informatyczny kącik olimpijski

Drogi Stanowe

Jakub Radoszewski

o artykule ...

  • Publikacja w Delcie: styczeń 2014
  • Publikacja elektroniczna: 01-01-2014
  • Wersja do druku [application/pdf]: (63 KB)

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 math 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 math W odpowiedzi na takie zapytanie mamy stwierdzić, czy z jakiegokolwiek wierzchołka zbioru math wychodzi jakaś krawędź poza ten zbiór; innymi słowy, czy wierzchołki zbioru math 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 math o numerze math dodajemy po jednym nowym elemencie do list sąsiedztwa wierzchołków math i  math 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:

display-math

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:

display-math

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:

display-math

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 math liczbę wierzchołków grafu, a przez math – łączną liczbę krawędzi wstawionych kiedykolwiek do grafu. Niech math (dla math) oznacza liczbę wierzchołków występujących w  math-tym zapytaniu, math Całkiem naturalnie jako rozmiar danych wejściowych przyjmiemy math

Jak teraz wyrazić względem math koszt obsługi wszystkich zapytań? Zauważmy, że w  math-tym zapytaniu będziemy musieli przejrzeć co najwyżej math krawędzi. Faktycznie, tyle krawędzi (liczonych w obie strony) ma klika o  math 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 math  Z drugiej strony, w każdym zapytaniu przejrzymy łącznie co najwyżej math  krawędzi. Ostatecznie koszt zapytania to math  Skorzystajmy teraz z nierówności między minimum a średnią geometryczną:

display-math

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

display-math

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 math Jeśli suma ta jest nieparzysta, to na pewno z  math 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  math 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 math będziemy przechowywać (jako math) bitową alternatywę wykluczającą wag krawędzi incydentnych z tym wierzchołkiem. Przy wstawianiu i usuwaniu krawędzi tablicę math możemy aktualizować w czasie stałym – w obu przypadkach wystarczy do-xor-ować wagę krawędzi do math-ów jej końców. W przypadku wstawiania będzie to:

pict

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

display-math

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 math  gdzie math  jest ograniczeniem górnym na wagę krawędzi. Przyjmując, naturalnie, math  lub math  otrzymujemy rzeczywiście bardzo małe prawdopodobieństwo błędu.