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