Jaskinia
W tym kąciku omówimy zadanie Jaskinia, które pojawiło się w zeszłym roku na Akademickich Mistrzostwach Polski w Programowaniu Zespołowym.
Tytułowa jaskinia składa się z
komnat połączonych
korytarzami,
które tworzą drzewo. Grotołazi chcą podzielić się na kilka grup, a następnie
chcą przydzielić każdej grupie zbiór komnat, tak by każda komnata została
przydzielona dokładnie jednej ekipie grotołazów oraz by każda grupa dostała
tyle samo komnat do zbadania i była w stanie poruszać się pomiędzy
przydzielonymi komnatami bez przechodzenia przez komnaty innych ekip.
Na ile grup mogą podzielić się grotołazi?
Poniższa obserwacja daje warunek konieczny i wystarczający na istnienie podziału:
Lemat. Drzewo o
wierzchołkach można podzielić na spójne
kawałki rozmiaru
wtedy i tylko wtedy, gdy w tym drzewie znajduje
się dokładnie
krawędzi, które łączą poddrzewa o rozmiarach
będących wielokrotnościami
Dowód (prosty). Jeśli podzieliliśmy drzewo na kawałki rozmiaru
zgodnie
z warunkami zadania, to tych kawałków jest
Krawędzi, które
łączą wierzchołki należące do różnych kawałków, jest dokładnie
a ponieważ poddrzewa połączone takimi krawędziami
składają się z całych kawałków, wobec tego rozmiar każdego z tych
poddrzew jest podzielny przez
Krawędzie wewnątrz kawałków
nie mają tej własności.
W drugą stronę: usuwamy
kolejno wszystkie
„dobrych” krawędzi. Po każdym usunięciu
dostajemy zbiór niepustych kawałków o rozmiarach podzielnych przez
Na końcu zostanie
kawałków, czyli wszystkie muszą
mieć rozmiar
Powyższa obserwacja pozwala nam na stworzenie następującego
rozwiązania: ukorzeniamy drzewo w dowolnym wierzchołku i dla każdego
przechodzimy drzewo od liści do korzenia, zliczając „dobre”
krawędzie, tzn. takie, które łączą poddrzewa o rozmiarach będących
wielokrotnościami
(rozmiary poddrzew obliczamy na bieżąco
prostym programowaniem dynamicznym).
Złożoność czasowa tego rozwiązania to
gdzie
oznacza liczbę dzielników
Wśród liczb
możemy znaleźć takie, które mają dość dużo dzielników
(rekord to
), zatem powyższe rozwiązanie jest
niezbyt szybkie.
Można to zrobić sprytniej, wykonując tylko jedno przeszukiwanie drzewa.
Rozważmy bowiem krawędź, która łączy dwa poddrzewa o rozmiarach
oraz
i zastanówmy się, dla jakich
będzie ona
„dobrą” krawędzią. Ano, dla takich
które dzielą zarówno
jak i
zatem dla wszystkich dzielników liczby
Możemy zatem postąpić następująco. W tablicy
będziemy zliczać „dobre” krawędzie. W pierwszym kroku
przechodzimy drzewo od liści do korzenia i dla każdej krawędzi zwiększamy
licznik
Przejście drzewa wykonujemy w czasie
gdyż dla każdej krawędzi obliczenie największego
wspólnego dzielnika możemy wykonać w czasie
korzystając
z algorytmu Euklidesa.
Następnie przeglądamy dzielniki
w kolejności rosnącej i dla każdego
takiego dzielnika
zwiększamy o wartość
liczniki
dla
Jeśli dla każdego
w poszukiwaniu jego
dzielników przejrzymy wszystkie liczby mniejsze od
to ten krok
wykonamy w czasie
czyli czasie proporcjonalnym do sumy
dzielników
czyli
Odpowiedzią są wszystkie liczby
które spełniają
Złożoność czasowa tego rozwiązania to
Przejście drzewa moglibyśmy zrealizować
w czasie liniowym, gdybyśmy znali dla każdego
wartość
Można te wartości obliczyć następującym
algorytmem, który przypomina metodę sita Eratostenesa:

Po wykonaniu powyższego kodu będziemy mieli
dla
każdego
Dla każdego
wewnętrzna
pętla wykona się
razy, zatem w całym algorytmie wykona się
razy. Ostatecznie daje nam to algorytm o złożoności czasowej