Najważniejsze spostrzeżenie
Dla -tego mieszkańca, przez oznaczmy zbiór mieszkańców, których numery telefonu przechowuje -ty mieszkaniec. Są to dokładnie te numery, które podaje -wszy wiersz wejścia. Dalej, przez oznaczamy zbiór po wstawieniu elementu (tj. ). Załóżmy, że liczba miast spełnia . Okazuje się, że wówczas mieszkańcy i mieszkają w tym samym mieście wtedy i tylko wtedy, gdy . Przykładowo, w teście przykładowym do zadania mamy .
Spróbujmy to uzasadnić. Jeśli mieszkańcy i mieszkają w jednym mieście, to numery telefonu, które przechowują, to numery do pozostałych mieszkańców tego miasta oraz do wszystkich mieszkańców każdego miasta połączonego z nim drogą. W takim razie zbiory oraz różnią się tylko tym, że w nie ma elementu , a w nie ma elementu . Czyli rzeczywiście mamy .
Załóżmy teraz, że dla mieszkańców i zachodzi . Zastanówmy się, czy jest możliwe, że w tej sytuacji mieszkańcy i mieszkają w różnych miastach. Spróbujemy tak założyć, co doprowadzi nas do sprzeczności. Nie mogą to być miasta niepołączone bezpośrednio drogą, gdyż wtedy zbiór nie zawierałby elementu , a siłą rzeczy taki element należy do zbioru . W takim razie mieszkańcy i musieliby mieszkać w dwóch różnych miastach, powiedzmy, odpowiednio, mieście i mieście , takich że i są połączone drogą. Założyliśmy, że , więc to oznacza, że co najmniej jedno z miast , – dla ustalenia uwagi niech będzie to miasto – jest połączone z jakimś jeszcze innym miastem . Wówczas miasto nie może być połączone drogą z miastem , gdyż wtedy z miasta do miasta można byłoby dojechać dwiema drogami bez zawracania – jedną bezpośrednią, drugą przez miasto . W mieście mieszka co najmniej jeden mieszkaniec; niech będzie to mieszkaniec . Wówczas należy do zbioru , ale nie należy do zbioru , co daje nam żądaną sprzeczność.
Co jeśli
Jeśli , to dla każdego mieszkańca zbiór zawiera po prostu wszystkich mieszkańców . Dokładnie tak samo jest w przypadku . Tak więc te dwa przypadki są nierozróżnialne. Natomiast jeśli , łatwo zauważyć, że któryś ze zbiorów nie zawiera jakiegoś elementu.
Jeśli , to musi być tylko miasto, gdyż w każdym mieście jest co najmniej jeden mieszkaniec. Jeśli zaś , to w tej sytuacji mieszkańców możemy podzielić jakkolwiek na dwa miasta. Jest to wymagane, gdyż powinniśmy maksymalizować w wyniku liczbę .
Wyznaczanie wartości
Aby uzyskać 50% punktów, wystarczyło poprawnie odtworzyć liczbę miast . W tym celu wystarczy posortować zbiory i zliczyć, ile wśród nich pojawia się różnych zbiorów. Jeśli okaże się, że wszystkie zbiory są takie same, wiemy, że . Wypisujemy wtedy , jeśli , a w przeciwnym razie . W przeciwnym razie liczba różnych zbiorów to właśnie szukana wartość .
Jak szybko sortować?
Elementy zbioru możemy reprezentować w postaci uporządkowanego ciągu liczb. Wystarczy skorzystać z ciąg liczb reprezentującego zbiór , który mamy na wejściu, a następnie wstawić we właściwe miejsce element . W ten sposób zbiory możemy sortować jak ciągi liczb, porównując je leksykograficznie (jak napisy).
Jakiego algorytmu użyć do sortowania ciągów liczb? Najprościej skorzystać z algorytmu std::sort z biblioteki standardowej C++, który automatycznie sortuje takie ciągi (przechowywane np. w kontenerze vector) w kolejności leksykograficznej. Okazuje się, że jest to dostatecznie szybkie rozwiązanie. Można to stwierdzić w praktyce albo spróbować przeanalizować złożoność takiego algorytmu.
Przyjmijmy, że algorytm std::sort opiera się w dużej mierze na algorytmie QuickSort. W algorytmie tym wybieramy element dzielący i pozostałe elementy grupujemy na podstawie porównania z elementem dzielącym. W naszym przypadku elementami są ciągi liczb o łącznej długości . Koszt porównania leksykograficznego dwóch ciągów o długościach, odpowiednio, i , to . Łączny czas porównań, w przypadku, gdy elementem dzielącym jest ciąg o długości , jest więc proporcjonalny do .
W kolejnej fazie algorytmu mamy wywołanie rekurencyjne na elementach, które okazały się być mniejsze od elementu dzielącego, i na pozostałych elementach. Łatwo zauważyć, że podzielenie obu tych części również zajmie łącznie czas . Algorytm QuickSort w oczekiwanym przypadku ma głębokość rekursji , co łącznie daje oczekiwany czas .
Powyższa analiza jest zgrubna o tyle, że nie zakłada dogłębnej wiedzy o tym, jak działa algorytm std::sort. Jeśli chcielibyśmy przyspieszyć powyższy algorytm sortowania, moglibyśmy np. przypisać sortowanym ciągom hasze, będące małymi liczbami całkowitymi, i sortować te hasze. Można w tym celu użyć metody randomizowanej (hasze wielomianowe) lub deterministycznej (słownik podsłów bazowych). Obie te metody są opisane np. w książce Przygody Bajtazara. 25 lat Olimpiady Informatycznej. Wybór zadań.
Do sortowania napisów równej długości można użyć algorytmu sortowania pozycyjnego (RadixSort), opisanego np. w książce Cormena i in. Wprowadzenie do algorytmów. Algorytm ten działa w czasie liniowym od sumy długości napisów, zakładając, że alfabet, z którego pochodzą symbole napisów, jest niezbyt duży. Okazuje się, że po pewnym namyśle algorytm ten można przystosować do sortowania zbiorów w naszym zadaniu w łącznym czasie . Pozostawiamy to jako ciekawe ćwiczenie dla Czytelnika.
Wyznaczanie dróg
Przy okazji wyznaczania liczby miast możemy od razu przypisywać kolejnym mieszkańcom numery miast. Pomijając przypadek , równe zbiory to równe numery miast. Niech oznacza numer miasta przypisany w ten sposób mieszkańcowi .
Aby odtworzyć połączenia między miastami, dla każdego mieszkańca i każdego mieszkańca należącego do zbioru , tworzymy drogę między miastami i , jeśli tylko te dwa miasta są różne. Następnie wystarczy posortować wszystkie drogi i wypisać je bez duplikatów, dbając o to, aby drogę łączącą miasta i reprezentować za pomocą pary liczb i . Można do tego użyć algorytmów std::sort i std::unique, otrzymując czas . Pary można też posortować pozycyjnie w czasie , co w praktyce nie zmienia zbytnio czasu działania.
Zauważmy, że nie musimy sprawdzać, czy sieć dróg jest oszczędna zgodnie z warunkiem z treści zadania. Jest tak dlatego, że podobnie jak liczba miast, sieć dróg jest wyznaczona jednoznacznie na podstawie wejścia (o ile ), a w zadaniu mamy gwarancję, że wejście jest poprawne.
Drzewo
Warto choć na koniec przedstawić interpretację treści zadania w języku teorii grafów. Miasta reprezentują wierzchołki grafu, a dwukierunkowe drogi to krawędzie nieskierowane łączące pary wierzchołków. Warunek z treści zadania, że z każdego miasta do każdego innego można dojechać za pomocą sieci dróg bez zawracania na dokładnie jeden sposób, oznacza, że graf jest drzewem.