• Nie Znaleziono Wyników

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.