網路流(最大流,最小割)基礎入門詳解
網路流基本定義:
源點:有n個點,有m條有向邊,有一個點很特殊,只出不進,叫做源點。
匯點:另一個點也很特殊,只進不出,叫做匯點。
容量和流量:每條有向邊上有兩個量,容量和流量,從i到j的容量通常用c(u,v)表示,流量則通常是f(u,v).
殘餘網路:r(u,v) = c(u,v) – f(u,v),其中c(u,v) 表示容量,f(u,v)表示流量,r(u,v)表示殘量網路
通常可以把這些邊想象成道路,流量就是這條道路的車流量,容量就是道路可承受的最大的車流量。很顯然的,流量<=容量。而對於每個不是源點和匯點的點來說,可以類比的想象成沒有儲存功能的貨物的中轉站,所有“進入”他們的流量和等於所有從他本身“出去”的流量。
網路流的3個性質:
1、容量限制: f[u,v]<=c[u,v]
2、反對稱性:f[u,v] = - f[v,u]
3、流量平衡: 對於不是源點也不是匯點的任意結點,流入該結點的流量和等於流出該結點的流量和。
只要滿足這三個性質,就是一個合法的網路流.
最大流問題:
最大流問題,就是求在滿足網路流性質的情況下,源點 s 到匯點 t 的最大流量。
問題:給出n個河流,m個點,以及每個河流的容量,求從1到m點的最大流量。
求解思路:
首先,假如所有邊上的流量都沒有超過容量(不大於容量),那麼就把這一組流量,或者說,這個流,稱為一個可行流
對於這種沒有給出流量f的問題,稱為零流問題,即所有的流量都是0的流。
(1).我們就從這個零流開始考慮,假如有這麼一條路,這條路從源點開始一直一段一段的連到了匯點,並且,這條路上的每一段都滿足流量<=容量。
(2).那麼,我們一定能找到這條路上的每一段的流量的值當中的最小值flow。我們把這條路上每一段的流量都減去這個flow,一定可以保證這個流依然是可行流,這是顯然的,然後再將這條路上的每一段的反向都加上這個flow
(3).然後將每次的流量都加上flow,這樣我們就得到了一個更大的流,他的流量是之前的流量+flow,而這條路就叫做增廣路。我們不斷地從起點開始尋找增廣路,每次都對其進行增廣,直到源點和匯點不連通,也就是找不到增廣路為止。
(4).當找不到增廣路的時候,當前的流量就是最大流,這個結論非常重要。
注意事項:
1.反向邊系列:
反向邊求法:u->v的反向邊f(v,u)=c(v,u)-f(v,u)=c(v,u)+f(u,v);
為什麼要求反向邊:在做增廣路時可能會阻塞後面的增廣路,或者說,做增廣路本來是有個順序才能找完最大流的。但我們是任意找的,為了修正,就每次將流量加在了反向弧上,讓後面的流能夠進行自我調整。
例如:下邊這個例子1位源點,4為匯點
如果我們第一次找的是1->2->3->4這條增廣路徑可以得到一個可行流為1,執行操作(2)後圖形變成了如下:
這時我們再找增廣路徑,你會發現已經無法從1到達4了,所以這個時候的最大流就是1了,但顯然這是錯誤的,如果我們分別走1->2->3和1->3->4這條路就可以得到一個為2的可行流。
為什麼會出現這樣的錯誤呢?
那是因為我們找從1到4的路徑是隨機的沒有任何技巧可言,並不能保證從1到4的某條路就是最大流,但可以確定的是這條路徑很可能會破壞其他路徑,而破壞的路徑可能是構成最大流的其中一條路徑。所以這個時候我們要給程式一個後悔的機會,即新增一條反向路徑。具體看下邊這個過程:
我們在找到1->2->3->4這條增廣路徑後需要修改原圖如下:
這樣修改圖後,可以發現,這個時候還存在增廣路徑1->3->2->4,然後修改圖如下:
加上之前的1->2->3->4,此時的最大流為2。
那麼,這麼做為什麼會是對的呢?
事實上,當我們第二次的增廣路走3-2這條反向邊的時候,就相當於把2-3這條正向邊已經是用了的流量給“退”了回去,不走2-3這條路,而改走從2點出發的其他的路也就是2-4。
如果這裡沒有2-4怎麼辦?
這時假如沒有2-4這條路的話,最終這條增廣路也不會存在,因為他根本不能走到匯點
同時本來在3-4上的流量由1-3-4這條路來“接管”。而最終2-3這條路正向流量1,反向流量1,等於沒有流。
CODE:
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
int Map[210][210];
int pre[210];
int flow[210];
int n,m;
int bfs(int s,int t)
{
queue<int> Q;
memset(pre,-1,sizeof(pre));
pre[s] = 0;
flow[s] = INF;
Q.push(s);
while(!Q.empty()){
int index = Q.front();
Q.pop();
if(index == t)
break;
for(int i=1;i<=m;i++){
if(i!=s && Map[index][i]>0&&pre[i]==-1){
pre[i] = index;
flow[i] = min(flow[index],Map[index][i]);
Q.push(i);
}
}
}
if(pre[t] == -1)///到不了匯點
return -1;
else
return flow[t];
}
int Max_flow(int s,int t)
{
int inc=0,sumflow=0;
while(bfs(s,t)!=-1){
inc = bfs(s,t);
int k = t;
while(k!=s){
int last = pre[k];
Map[last][k] -= inc;
Map[k][last] += inc;
k = last;
}
cout<<"曾廣:"<<inc<<endl;
sumflow += inc;
}
return sumflow;
}
int main()
{
int u,v,c;
while(~scanf("%d %d",&n,&m)){
memset(Map,0,sizeof(Map));
memset(flow,0,sizeof(flow));
for(int i=0;i<n;i++){
scanf("%d %d %d",&u,&v,&c);
if(u == v) continue;
Map[u][v] += c;
}
cout<<Max_flow(1,m)<<endl;
}
return 0;
}
最小割
割的定義:設Ci為網路G中一些弧的集合,若從G中刪去Ci中的所有弧能使得從源點Vs到匯點Vt的路集為空集時,稱Ci為Vs和Vt間的一個割。(注意,必須是刪除Ci中的所有邊)
最小割:圖中所有的割中,邊權值和最小的割為最小割。
最小割與最大流的關係
我以下邊這個例子來說明最大流和最小割之間的關係:
從1到4,中間經過2,3兩節點,問此時的最大流是多少?
首先找一條從1到4的路徑[1,2,4],該路徑的最大流量是min(2,3)=2,因為[1,2]上面的容量已經被用了,所以路徑[1,2,3,4]就行不通了,割去[1,2]後圖變成了以下形式:
此時再找從1到4的路徑[1,3,4],路徑的最大流量是min(3,6)=3.割去[b,t]後,圖如下:
此時就不存在從S到t的可行路徑了,則結束最大流的查詢。此時的最大流是2+3=5,被割的邊容量和是2+3=5,即最大流=最小割。從這兩個例子我們已經能理解最大流和最小割大體的含義了,也發現最大流的確和最小割是相等的從數值上來看。但只從這兩個小例子就證明最大流和最小割相等是絕對不嚴格的,嚴格的數學證明可自行百度相關資料。