Zadanie: Przemytnicy
2.12.2. Maksymalny przepływ dla krawędzi jednostkowych
2.12.2. Maksymalny przepływ dla krawędzi jednostkowych
W wielu zadaniach, zwi¸azanych z wyznaczaniem maksymalnego przepływu, pojawiaj¸a się różne dodatkowe ograniczenia, które niekiedy ułatwiaj¸a b¸adź utrudniaj¸a rozwi¸azywany prob-lem. Jednym z takich ograniczeń może być założenie, że przez każd¸a krawędź może przepływać co najwyżej jednostkowa wielkość przepływu. Problem tego typu nazywać będziemy poszuki-wać maksymalnego jednostkowego przepływu (z krawędziami o jednostkowej przepustowości).
Algorytm z poprzedniego rozdziału jak najbardziej nadaje się do rozwi¸azywania tego prob-lemu, jednak istnieje nie tylko prostszy, ale również i szybszy algorytm.
Przedstawiony w tym rozdziale algorytm służy do wyznaczania maksymalnego jednos-tkowego przepływu w czasie O(n8/3) dla grafów skierowanych. Podobnie jak w algorytmie Dinica, pomysł polega na wyszukiwaniu kolejnych ścieżek powiększaj¸acych. Fakt, iż każda krawędź na wyznaczanych ścieżkach powiększaj¸acych ma tak¸a sam¸a niewykorzystan¸a prze-pustowość, pozwala na prosty sposób usuwania krawędzi nasyconych, oraz dodawania ich odpowiedników o przeciwnym zwrocie. Wyznaczanie ścieżek powiększaj¸acych realizowane jest przez dwie fazy — przeszukiwanie BFS, które dokonuje podziału wierzchołków na warstwy, a następnie przeszukiwanie w gł¸ab, na skutek którego odwracane s¸a skierowania krawędzi należ¸acych do znalezionych ścieżek powiększaj¸acych. W przypadku, gdy w grafie nie ist-nieje już więcej ścieżek powiększaj¸acych prowadz¸acych ze źródła do ujścia, algorytm kończy działanie, zwracaj¸ac jako wynik liczbę wyznaczonych dotychczas ścieżek. Przykład działa-nia został przedstawiony na rysunku 2.13. Funkcja int UnitFlow(int, int) z listingu 2.44 realizuje opisany algorytm, przyjmuj¸ac jako parametry numery wierzchołków stanowi¸acych odpowiednio źródło oraz ujście wyznaczanego przepływu. Jako wynik zwracana jest wielkość maksymalnego jednostkowego przepływu. Funkcja wymaga wzbogacenia struktury wierz-chołków o pola int toraz int s.
W przypadku korzystania z tej funkcji należy pamiętać, że struktura grafu ulega mody-fikacji — zostaje zmienione skierowanie krawędzi wchodz¸acych w skład ścieżek powiększaj¸a-cych. Funkcja może być wykorzystywana zarówno dla grafów skierowanych, jak i nieskierowa-nych (w tym drugim przypadku jednak, modyfikacje wykonane na grafie zaburzaj¸a reprezen-tację krawędzi nieskierowanych przy użyciu pól int rev).
Listing 2.44: Implementacja funkcji int Graph<V,E>::UnitFlow(int, int) // Funkcja odwraca skierowanie krawędzi e wychodzącej z wierzchołka v 01 void mvFlow(intv, Ed & e) {
02 int u = e.v;
Listing 2.44: (c.d. listingu z poprzedniej strony) 03 g[u].PB(e);
04 g[u].back().v = v;
05 swap(g[v].back(), e);
06 g[v].pop back();
07} 08 int Ue;
// Funkcja szuka ścieżki prowadzącej do wierzchołka Ue (ujścia) przy użyciu // przeszukiwania w głąb
09 bool UFDfs(intv) {
// Jeśli wierzchołek jest ujściem, to została znaleziona ścieżka poszerzająca 10 if (v == Ue) return true;
11 g[v].s = 1;
// Dla każdej krawędzi wychodzącej z wierzchołka...
12 FOREACH(it, g[v])
// Jeśli łączy ona kolejne warstwy oraz wierzchołek docelowy nie był jeszcze // odwiedzony, to odwiedź go...
13 if (g[it->v].t == 1 + g[v].t && !g[it->v].s && UFDfs(it->v)) {
// W przypadku znalezienia ścieżki poszerzającej, zamień skierowanie krawędzi 14 mvFlow(v, *it);
15 return true;
16 }
17 return false;
18}
// Właściwa funkcja wyznaczająca maksymalny przepływ jednostkowy między // wierzchołkami v1 i v2
19 int UnitFlow(intv1, int v2) { 20 intres = 0;
21 Ue = v2;
22 while(1) {
// Wyznacz drzewo przeszukiwania BFS 23 Bfs(v1);
// Jeśli ujście nie zostało odwiedzone, to nie da się powiększyć przepływu 24 if (g[v2].t == -1) break;
25 FOREACH(it, g) it->s = 0;
// Dla każdej krawędzi wychodzącej z wierzchołka źródłowego, jeśli istnieje // ścieżka poszerzająca zawierająca tę krawędź, to powiększ przepływ
26 FOREACH(it, g[v1]) if (UFDfs(it->v)) {
27 res++;
28 mvFlow(v1, *it--);
29 }
30 }
31 returnres;
32}
0
Rysunek 2.13:Przykład wyznaczania maksymalnego jednostkowego przepływu dla grafu z rysunku (a) pomiędzy wierzchołkiem numer 0, a wierzchołkiem numer 5. (b) Stan skierowania krawędzi grafu po wyznaczeniu pierwszej ścieżki powiększaj¸acej 0 → 1 → 2 → 5. (c) Stan skierowania krawędzi po wyznaczeniu drugiej ścieżki powiększaj¸acej 0 → 3 → 4 → 5. W grafie nie ma już więcej ścieżek 0 ; 5, zatem wyznaczony przepływ jest maksymalny.
Listing 2.45: Wartość wyznaczonego maksymalnego przepływu dla grafu z rysunku 2.13.a między wierzchołkami 0 i 5 przez funkcjęGraph<V,E>::UnitFlow(int, int)
Przeplyw miedzy wierzcholkami 0 i 5 wynosi 2
Listing 2.46: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 2.45. Pełny kod źródłowy programu znajduje się w pliku unitflow.cpp
01 struct Ve {};
// Wzbogacenie struktury wierzchołków o pole wymagane przez funkcję UnitFlow 02 struct Vs {
03 int t,s;
04};
05 intmain() {
06 int n, m, s, f, b, e;
// Wczytaj liczbę wierzchołków i krawędzi w grafie oraz numer wierzchołka // początkowego i końcowego
07 cin >> n >> m >> s >> f;
// Skonstruuj graf o odpowiednim rozmiarze oraz dodaj do niego wymagane krawędzie 08 Graph<Vs, Ve> g(n);
09 REP(x,m) {
10 cin >> b >> e;
11 g.EdgeD(b, e);
12 }
// Wypisz wielkość wyznaczonego maksymalnego przepływu
13 cout << "Przeplyw miedzy wierzcholkami " << s << " i " << f <<
14 " wynosi " << g.UnitFlow(s, f) << endl;
15 return 0;
16}
0
Rysunek 2.14: (a) Skierowany graf z kosztami przypisanymi krawędziom. (b) Wyznaczony maksymalny przepływ o wielkości 2 i koszcie 109 dla krawędzi o wagach jednostkowych. (c) Maksy-malny przepływ o wielkości 2 i miniMaksy-malnym koszcie 75.
2.12.3. Najtańszy maksymalny przepływ dla krawędzi jednostkowych Na problem wyznaczania maksymalnego przepływu z poprzed- Literatura [CON] - 4.7 niego rozdziału można narzucić dodatkowe wymaganie. Załóżmy,
że dla wszystkich krawędzi analizowanej sieci dodane zostały nieu-jemne koszty. Koszt utrzymania krawędzi jest równy iloczynowi jej
kosztu oraz wielkości przepływu. Kosztem utrzymania całej sieci jest suma kosztów utrzyma-nia wszystkich krawędzi. Nowo postawiony problem polega na wyznaczeniu w sieci maksy-malnego przepływu jednostkowego, którego koszt jest minimalny.
Algorytm, realizowany przez prezentowan¸a funkcjęPII Graph<V,E>::MinCostFlow(int, int)z listingu 2.47 działa w czasie O(n ∗m ∗u), gdzie u jest wielkości¸a wyznaczanego maksy-malnego przepływu. Pierwszym oraz drugim parametrem funkcji s¸a odpowiednio źródło oraz ujście. Funkcja jako wynik swojego działania zwraca parę liczb naturalnych — wielkość wyz-naczonego maksymalnego przepływu oraz jego koszt. Do działania funkcji, wymagane jest wzbogacenie struktury wierzchołków grafu o dodatkowe pola int t oraz int s, natomiast krawędzi o pole int l, w którym umieszcza się koszt krawędzi.
Zasada działania algorytmu jest podobna do innych algorytmów wyznaczaj¸acych maksy-malny przepływ. Tym razem jednak, w celu zapewnienia minimalności kosztu, do wyznacza-nia ścieżek powiększaj¸acych wykorzystywany jest algorytm Bellmana-Forda. Zauważmy, że jeśli poszukujemy przepływu jednostkowego o minimalnym koszcie, którego wielkość wynosi 1 (przepływ taki składa się z dokładnie jednej ścieżki powiększaj¸acej), to ścieżkę powięk-szaj¸ac¸a możnaby wyznaczyć przy użyciu algorytmu Dijkstry — sumaryczny koszt utrzymania krawędzi na ścieżce można potraktować jako jej długość. Zatem najkrótsza znaleziona ścieżka między źródłem a ujściem jest zgodnie z nasz¸a definicj¸a najtańsza.
Po wyznaczeniu pierwszej ścieżki powiększaj¸acej, należy zamienić zwroty oraz koszty krawędzi do niej należ¸acych na przeciwne, a następnie przyst¸apić do wyszukiwania kolejnych ścieżek powiększaj¸acych. Ze względu jednak na zamianę kosztów krawędzi, w grafie pojawi¸a się krawędzie z ujemnymi wagami, co powoduje, że algorytm Dijkstry nie może zostać już użyty — zamiast niego można jednak skorzystać z algorytmu Bellmana-Forda.
Korzystaj¸ac z funkcjiPII Graph<V,E>::MinCostFlow(int, int)należy pamiętać o tym, że dokonuje ona modyfikacji struktury grafu poprzez zamiany zwrotów krawędzi oraz zmiany ich kosztów.
Listing 2.47: Implementacja funkcjiPII Graph<V,E>::MinCostFlow(int, int) // Funkcja wyznacza wielkość oraz koszt maksymalnego, jednostkowego przepływu o // minimalnym koszcie w sieci między wierzchołkami v1 oraz v2. Na skutek jej // działania graf podlega modyfikacjom.
01PII MinCostFlow(intv1, int v2) { 02 int n = SIZE(g);
03 PII res = MP(0, 0);
04 vector < typename vector<Ed>::iterator > vit(SIZE(g));
05 while (1) {
// Ustaw aktualne odległości dla wszystkich wierzchołków (poza źródłem) na // INF oraz zaznacz wierzchołek v1 jako źródło wyszukiwania
06 FOREACH(it, g) it->t = INF;
07 g[v1].t = 0;
08 g[v1].s = -1;
// Wykonaj co najwyżej SIZE(g) faz wyznaczania najkrótszych ścieżek metodą // Bellmana-Forda
09 for(int chg = 1, cnt = 0; chg && ++cnt < SIZE(g);) {
10 chg = 0;
11 REP(i, SIZE(g)) FOREACH(it, g[i]) 12 if (g[i].t + it->l < g[it->v].t) { 13 g[it->v].t = g[i].t + it->l;
14 g[it->v].s = i;
15 vit[it->v] = it;
16 chg = 1;
17 }
18 }
// Jeśli nie wyznaczono ścieżki między v1 a v2, to przerwij 19 if (g[v2].t == INF) break;
// Zwiększ wynik 20 res.ST++;
21 res.ND += g[v2].t;
// Odwróć skierowanie krawędzi na wyznaczonej ścieżce oraz zmień znaki wag // krawędzi na przeciwne
22 intx = v2;
23 while(x != v1) { 24 intv = g[x].s;
25 swap(*vit[x], g[v].back());
26 Ed e = g[v].back();
27 e.l *= -1;
28 e.v = v;
29 g[x].PB(e);
30 g[x = v].pop back();
31 }
32 }
33 return res;
34}
Listing 2.48: Wielkość oraz koszt maksymalnego przepływu o minimalnym koszcie wyznaczonego przez funkcję PII Graph<V,E>::MinCostFlow(int, int) wykonanej dla grafu z rysunku 2.14, źródła 0 i ujścia 7.
Wyznaczanie przeplywu z 0 do 7
Wielkosc przeplywu: 2, koszt przeplywu: 75
Listing 2.49: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 2.48. Pełny kod źródłowy programu znajduje się w pliku mincostflow.cpp
// Wzbogacenie dla wierzchołków oraz krawędzi wymagane przez funkcję // MinCostFlow
01 struct Vs { 02 intt, s;
03};
04 struct Ve { 05 intl;
06};
07 int main() {
08 intn, m, s, f, b, e;
// Wczytaj liczbę wierzchołków i krawędzi oraz źródło i ujście wyznaczanego // przepływu
09 cin >> n >> m >> s >> f;
// Skonstruuj graf o odpowiedniej liczbie wierzchołków oraz dodaj do niego // wymagane krawędzie
10 Graph<Vs, Ve> g(n);
11 Ve l;
12 REP(x, m) {
13 cin >> b >> e >> l.l;
14 g.EdgeD(b, e, l);
15 }
// Wyznacz maksymalny, najtańszy przepływ oraz wypisz wynik
16 cout << "Wyznaczanie przeplywu z " << s << " do " << f << endl;
17 PII res = g.MinCostFlow(s, f);
18 cout << "Wielkosc przeplywu: " << res.ST;
19 cout << ", koszt przeplywu: " << res.ND << endl;
20 return0;
21}
Ćwiczenia
Proste Średnie Trudne
acm.uva.es - zadanie 10092 acm.uva.es - zadanie 10480 acm.uva.es - zadanie 10546 acm.uva.es - zadanie 10330 acm.sgu.ru - zadanie 176 acm.sgu.ru - zadanie 212
acm.sgu.ru - zadanie 194
0
Rysunek 2.15:(a) Przykładowy graf o maksymalnym skojarzeniu wielkości 3. Krawędzie należ¸ace do przykładowego maksymalnego skojarzenia zostały pogrubione. (b) Graf o doskonałym skojarzeniu wielkości 4.