Runda 3: Moda na zwycięstwo (rozwiązanie)
01.12.2009
Autor zadania: Jarosław Gomułka Na początku zastanówmy się nad najprostszą poprawną metodą rozwiązania tego problemu. Możemy przeiterować się po wszystkich możliwych okresach czasu. Następnie sprawdzić które osoby były ze sobą w związku w choć jednym odcinku w tym okresie, dodać do grafu G krawędzie pomiędzy osobami z każdego z tych związków. Sprawdzenie czy okres spełnia warunek z treści zadania to przeiterowanie się po synach każdej osoby i sprawdzenie, czy istnieje ścieżka pomiędzy numerami tych osób w grafie G int ans = nieskończoność; Jeśli zwrócona wartość wynosi nieskończoność to należy wypisać NIE. Czas działania tego algorytmu jest zdecydowanie za duży nawet dla najmniejszych danych. Możemy go jednak poprawić poprzez zauważenie pewnej prawidłowości: Niech C oznacza zbiór numerów odcinków w których zaczynał się lub kończył się jakiś związek. Czas startu i końca, conajmniej jednego najkrótszego szukanego okresu należy do zbioru C. Dowód. Jeśli start nie należy do C to możemy zwiększyć start o 1 i nie musimy usuwać z G żadnego związku. Jeśli koniec nie należy do C to możemy zmniejszyć koniec o 1 i nie musimy usuwać z G żadnego związku.
int ans = nieskończoność; Złożoność tego algorytmu to O(m^2 * (m +n*(n+m))) = O(m^2 *n * (n+m)) Jest to algorytm lepszy ale nadal daleki od optymalnego. Optymalizujmy go dalej! Założmy, że mamy obliczony graf G dla okresu czasu [C[i],C[j]]. Chcemy obliczyć graf G' dla okresu czasu [C[i],C[j+1]]. Zauważmy, że nie musimy od nowa budować całego grafu G', wystarczy dodać te związki do grafu G które zaczynają się w momencie C[j+1] Dodatkowo zauważmy że jeśli okres [C[i],C[j]] nie jest rozwiązaniem to nie ma sensu sprawdzać co się dzieje dla okresu [C[i],C[j+1]] gdyż wyniku i tak nie poprawimy. Algorytm wygląda teraz tak: int ans = nieskończoność; Złożoność tego algorytmu to O(m^2 * n * (n+m) ). W złożoności nic nie poprawiliśmy jednak w praktyce ten algorytm będzie działał dużo szybciej od poprzedniego algorytmu. Szukajmy dalszych optymalizacji! Załóżmy, że mamy obliczony graf G dla okresu czasu [C[i],C[j]], w tym momencie nastąpił BREAK, mamy obliczyć graf [C[i+1],C[i+2]] Zauważmy że ponieważ dla start=C[i], BREAK nastąpił dopiero dla końca równego C[j] więc nie znajdziemy okresu o szukanej własności w żadnym okresie czasu spośród [C[i+1],C[k]] gdzie k<j. Obliczymy więc odrazu graf G' dla okresu [C[i+1],C[j]]. Możemy to zrobić poprzez usunięcie z G tych krawędzi, które reprezentują związki zakończone w odcinku o numerze C[i]. Algorytm wygląda tak:
Ten algorytm na pierwszy rzut oka może wyglądać na optymalny. Każdy zwiazek jest dodawany i usuwany conajwyżej raz podczas całego algorytmu. Ilość sprawdzań czy okres spełnia warunek jest rzędu O(m). Złożoność to O(m*n*(n+m)) Jednak sprawdzanie czy istnieje ścieżka w grafie nadal jest za wolne. Pierwszym pomysłem jak to poprawić jest zastosowanie algorytmu Floyda-Warshala. Złożoność wyniesie wówczas O(m*n^3). Drugim pomysłem jest policzenie spójnych składowych grafu. Sprawdzenie czy istnieje ścieżka będzie wymagało sprawdzenia czy 2 wierzchołki należa do tej samej spójnej składowej. Złożoność wyniesie wówczas O(m*(n+m)). Obliczenie spójnych składowych zajmie O(n+m) ponieważ graf może być bardzo gęsty, pesymistycznie może zawierać m krawędzi. Dobrym pomysłem byłoby zastanowić się czy krawędzi może być mniej. Otóż może być! A więc chcemy mieć mniejszy graf G'. Bądźmy ambitniejsi! Chcemy by miał mniej niż n krawędzi! Niech istnieje_ścieżka(G,a,b) odpowiada na pytanie czy w grafie G istnieje ścieżka pomiedzy wierzchołkami a i b. Musimy się zastanowić jakie niezmienniki musi spełniać nasz graf. W końcu nie chcemy sprawić by algorym był niepoprawny.Interesuje nas istnienie ścieżek pomiędzy wierzchołkami, dokładniej dla każdego a i b musi zachodzić:
Założmy że graf G ma conajmniej n krawędzi. Ponieważ mamy n wierzchołków i >=n krawędzi w G istnieje cykl. Jeśli usuniemy jedną dowolną krawędz z tego cyklu niezmiennik istnienia ścieżek zostanie zachowany. Jednak nie możemy usunąć dowolnej krawędzi. W dalszej cześci algorytmu może zostać usunięta kolejna krawędź z cyklu co popsuje nam niezmiennik istnienia ścieżek. Usuńmy więc z cyklu tą krawędź która zostanie usunięta jako pierwsza! W ten sposób możemy pozbyć się wszystkich cykli. Graf bez cykli jest lasem. Las o n wierzchołkach ma mniej niż n krawędzi więc osiągneliśmy nasz cel! Precyzując, dodając krawędź do G nadajemy jej wage = numer odcinka w którym skończył sie związek reprezentowany przez tą krawędź. Przy każdym dodaniu krawędzi musimy sprawdzić czy nie pojawił się cykl i jeśli tak to usunąć najlżejszą krawędź należącą do tego cyklu. Wszystkie te operacje da się zrobić w O(n).
Jest to algorytm wzorcowy. Analiza złożoności: Mamy m krawędzi każda zostanie dodana i usunięta conajwyżej raz. Conajwyzej m razy obliczone zostaną spójne składowe G. Operacje dodania, usunięcia krawędzi, obliczania spójnych składowych zostaną obliczone w O(n). Złożoność sumaryczna O(n*m). W praktyce da się uzyskać złożoność O(n*logm), przy pomocy nieco bardziej skomplikowanych narzędzi niż te, które omówiliśmy. |
Copyright © 2008-2010 Wrocławski Portal Informatyczny
design: rafalpolito.com