Pochodzenie: Rozwi¸azanie:
Potyczki Algorytmiczne 2005 road.cpp
W Bajtocji jest n miast. Miasta s¸a poł¸aczone jednokierunkowymi drogami. Każda droga ł¸aczy tylko dwa miasta i nie przechodzi przez żadne inne. Niestety, nie zawsze z każdego miasta da się dojechać do każdego innego. Król Bajtazar postanowił rozwi¸azać ten problem.
Król ma świadomość, że budowanie nowych dróg jest bardzo kosztowne, a budżet Bajtocji nie jest zbyt zasobny. Dlatego też poprosił Cię o pomoc. Należy obliczyć minimaln¸a liczbę jednokierunkowych dróg, które trzeba zbudować, żeby z każdego miasta dało się dojechać do każdego innego miasta.
Zadanie
Napisz program, który:
wczyta opis istniej¸acej sieci dróg,
obliczy minimaln¸a liczbę dróg, które trzeba dobudować tak, aby z każdego miasta w Bajtocji dało się dojechać do każdego innego,
wypisze wynik.
Wejście
Pierwszy wiersz zawiera dwie liczby całkowite n i m (2 ¬ n ¬ 10 000, 0 ¬ m ¬ 100 000) oddzielone pojedynczym odstępem i oznaczaj¸ace, odpowiednio, liczbę miast i liczbę dróg w Bajtocji. Miasta s¸a ponumerowane od 1 do n. W każdym z kolejnych m wierszy znajduj¸a się dwie liczby całkowite, oddzielone pojedynczym odstępem. W i + 1 wierszu znajduj¸a się liczby ai i bi (1 ¬ ai, bi ¬ n dla 1 ¬ i ¬ m), reprezentuj¸a one jednokierunkow¸a drogę prowadz¸ac¸a z miasta ai do bi.
Wyjście
Pierwszy i jedyny wiersz wyjścia powinien zawierać dokładnie jedn¸a nieujemn¸a liczbę całkow-it¸a — minimaln¸a liczbę dróg, które trzeba zbudować w Bajtocji tak, aby z każdego miasta dało się dojechać do każdego innego miasta.
Przykład
acm.uva.es - zadanie 10731 acm.uva.es - zadanie 247 acm.uva.es - zadanie 125 spoj.sphere.pl - zadanie 51 acm.uva.es - zadanie 10510
2.5. Sortowanie topologiczne
Skierowany graf acykliczny (ang. directed acyclic graph), jest Literatura [WDA] - 23.4
[ASP] - 4.3.3 to graf, który nie posiada cykli. Innymi słowy, w grafie takim nie
istnieje para wierzchołków u i v poł¸aczonych ścieżkami u ; v i v ; u.
Sortowanie topologiczne skierowanego grafu G = (V, E) polega
na takim uporz¸adkowaniu wierzchołków ze zbioru V , aby dla każdej krawędzi (u, v) ∈ E, wierzchołek u znajdował się przed wierzchołkiem v. Sortowanie topologiczne daje się wyz-naczyć dla wszystkich grafów skierowanych nie zawieraj¸acych cykli. Do wykonania tego zada-nia można wykorzystać algorytm DFS — jedyne, co należy zrobić, to posortować wierzchołki w kolejności malej¸acych czasów przetworzenia f.
Załóżmy, że mamy dany skierowany acykliczny graf G oraz dwa wierzchołki u oraz v. Niech wartości f wyznaczone dla tych wierzchołków przez algorytm DFS będ¸a równe odpowiednio uf i vf, oraz załóżmy bez straty ogólności, że uf < vf. Zgodnie z naszym sposobem wyznacza-nia porz¸adku topologicznego, wierzchołek v zostanie umieszczony przed wierzchołkiem u.
Postępowanie takie jest zgodne z definicj¸a porz¸adku topologicznego, gdyż z warunku uf < vf oraz z acykliczności grafu G wynika, że w grafie tym nie ma krawędzi (u, v).
Algorytm DFS, oprócz zmiennych int f, wyznacza również inne, niepotrzebne z punktu widzenia sortowania topologicznego wartości, dlatego też implementacja funkcji void Graph
<V,E>::TopoSort()nie korzysta z przedstawionej wcześniej implementacji algorytmu DFS.
Wyznaczony porz¸adek topologiczny umieszczany jest w dodatkowych polach wierzchołków int t— pierwszy wierzchołek ma t¸a wartość równ¸a 0, drugi — 1... Listing 2.17 przedstawia implementację funkcji void Graph<V,E>::TopoSort().
0
5 3 4
1 2
0 1 4 3 2 5
(a) (b)
Rysunek 2.5: (a) Przykładowy skierowany graf acykliczny (b) jeden z możliwych porz¸adków topolog-icznych określonych na zbiorze wierzchołków grafu z rysunku (a)
Listing 2.17: Implementacja funkcji voidGraph<V,E>::TopoSort() 01 inttopo;
// Funkcja wykonująca algorytm DFS z wierzchołka v i aktualizująca wartości // zmiennych t
02 void TopoDfs(intv) {
// Jeśli wierzchołek nie był odwiedzony, to należy go odwiedzić 03 if (!g[v].t) {
// Zaznacz wierzchołek jako odwiedzony 04 g[v].t = 1;
// Odwiedź wszystkie wierzchołki, do których prowadzi krawędź z v 05 FOREACH(it, g[v]) TopoDfs(it->v);
// Zaktualizuj wartość t przetwarzanego wierzchołka 06 g[v].t = --topo;
07 } 08}
// Właściwa funkcja implementująca sortowanie topologiczne 09 void TopoSort() {
10 FOREACH(it, g) it->t = 0;
11 topo = SIZE(g);
// Odwiedź wszystkie wierzchołki w grafie 12 FORD(x, topo - 1, 0) TopoDfs(x);
13}
W wielu przypadkach, forma wyniku obliczana przez funkcję voidGraph<V,E>::TopoSort() jest nieporęczna w użyciu — często oczekiwanym rezultatem jest posortowana lista wierz-chołków grafu. Funkcja VI Graph<V,E>::TopoSortV(), której implementacja przedstaw-iona jest na listingu 2.18, jako wynik działania zwraca wektor liczb reprezentuj¸acych numery kolejnych wierzchołków w porz¸adku topologicznym.
Listing 2.18: Implementacja funkcjiVI Graph<V,E>::TopoSortV() 1 VI TopoSortV() {
2 VI res(SIZE(g));
// Wyznacz sortowanie topologiczne 3 TopoSort();
// Na podstawie wartości zmiennych t wierzchołków, wyznacz wektor z wynikiem 4 REP(x, SIZE(g)) res[g[x].t] = x;
Listing 2.18: (c.d. listingu z poprzedniej strony) 5 return res;
6 }
Czas działania algorytmu sortowania topologicznego to O(n + m), co wynika bezpośrednio z faktu, iż jest on modyfikacj¸a przeszukiwania grafu w gł¸ab.
Listing 2.19: Wynik wyznaczony przez funkcję VI Graph<V,E>::TopoSortV()dla grafu z ry-sunku 2.5.a
Kolejnosc topologiczna wierzcholkow: 0 1 3 4 2 5 Wierzcholek 0 ma pozycje 0 w porzadku topologicznym Wierzcholek 1 ma pozycje 1 w porzadku topologicznym Wierzcholek 2 ma pozycje 4 w porzadku topologicznym Wierzcholek 3 ma pozycje 2 w porzadku topologicznym Wierzcholek 4 ma pozycje 3 w porzadku topologicznym Wierzcholek 5 ma pozycje 5 w porzadku topologicznym
Listing 2.20: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 2.19. Pełny kod źródłowy programu znajduje się w pliku toposort str.cpp
01 struct Ve {};
// Wzbogacenie wierzchołka o pole t, w którym umieszczany jest wynik 02 struct Vs {
03 intt;
04};
05 int main() { 06 intn, m, b, e;
// Wczytaj liczbę wierzchołków oraz krawędzi, stwórz graf o odpowiedniej wielkości
07 cin >> n >> m;
08 Graph<Vs, Ve> g(n);
// Dodaj do grafu wszystkie krawędzie skierowane 09 REP(x,m) {
10 cin >> b >> e;
11 g.EdgeD(b, e);
12 }
// Wyznacz porządek topologiczny oraz wypisz wynik 13 VI res = g.TopoSortV();
14 cout << "Kolejnosc topologiczna wierzcholkow: ";
15 FOREACH(it, res) cout << *it << " ";
16 cout << endl;
17 REP(x, SIZE(g.g)) cout << "Wierzcholek " << x << " ma pozycje " <<
18 g.g[x].t << " w porzadku topologicznym" << endl;
19 return0;
20}