網絡流
網絡流的概念
在一個有向圖上選擇一個源點,一個匯點,每一條邊上都有一個流量上限(以下稱為容量),
即經過這條邊的流量不能超過這個上界,同時,除源點和匯點外,所有點的入流和出流都相等,
而源點只有流出的流,匯點只有匯入的流。這樣的圖叫做網絡流。
網絡流的相關定義
-
源點:有n個點,有m條有向邊,有一個點很特殊,只出不進,叫做源點。
-
匯點:另一個點也很特殊,只進不出,叫做匯點。
-
容量和流量:每條有向邊上有兩個量,容量和流量,從i到j的容量通常用c[i,j]表示,流量則通常是f[i,j].
通常可以把這些邊想象成道路,流量就是這條道路的車流量,容量就是道路可承受的最大的車流量。
很顯然的,流量<=容量。而對於每個不是源點和匯點的點來說,可以類比的想象成沒有存儲功能的貨物的中轉站,
所有“進入”他們的流量和等於所有從他本身“出去”的流量。
- 最大流:把源點比作工廠的話,問題就是求從工廠最大可以發出多少貨物,是不至於超過道路的容量限制,也就是,最大流。
Ford-Fulkerson 增廣路算法
該方法通過尋找增廣路來更新最大流,有 EK,dinic,SAP,ISAP 主流算法。
求解最大流之前,我們先認識一些概念。
最常用的就是dinic(據說隔壁treaker只會這一種)。
但是費用流需要用到EK算法,so,要學會EK算法,dinic;
增廣路:在圖中若一條從源點到匯點的路徑上所有邊的 剩余容量都大於 0 (注意是大於不是大於等於),這條路被稱為增廣路。
EK算法O(nm2)
求解思路:
從圖中找一條增廣路,然后增廣,怎么找?
1.從源點開始bfs,找到到匯點的一條路徑,並記錄這條路徑上所有邊剩余流量最小值,因為要找增廣
路,所以我們在bfs時要判斷一下邊的剩余容量是否為0,記得用一個pre數組記錄下路徑。
2.找到路徑后,對其進行增廣(代碼里的up函數),增廣就是把這條路徑的每條邊都減去這
些邊中剩余流量的最小值(bfs時記錄),反向邊加上這個最小值(關於方向邊下面再解釋)。
3.一直找增廣路增廣,直到不能增廣為止(找不到增廣路)。
可以看下面這張圖。
感謝SYCstudio的圖(本人較菜懶不會畫)。
上面我們提到了反向邊,下面我們解釋下為什么要建反向邊。(放圖,簡單圖我還是可以的現學的)
像我們上面這張圖,因為我們bfs時不能確定第一次走哪條邊,要是你像bmf一樣運氣不好,
如果bfs第一次找到的增廣路是1→3→2→1的話,我們最后求得的最大流就是1.
但是很明顯這張圖最大流是2,所以我們發現第一次找的增廣路不是最優的,這時候你就涼了。
那我們怎么解決呢反向邊,反向邊其實就是一個反悔的機會,也就是讓流過的流量流回去。
如果還不明白的話還學什么網絡流,下面模擬一下這個過程。
先說一下反向邊的一些東西,反向邊初始化為0,當正向邊剩余流量減少的是時候,
反向邊流量增加同樣的值,因為我們可以反悔的流量等於已經從這條邊正向流過的流量。
下面看一下我們是如何通過反向邊反悔的又要做圖qaq。
因為我不會畫反向邊,所以我們假設‘1/0’左邊那個數字表示正向變邊權,右邊是反向邊。
下面這張圖就是建完邊后的圖。
如果我們第一次找到的增廣路是1→3→2→1的話,總流量+1=1,圖就變成了
我們發現我們還可以找到增廣路1→2→3→4,總流量+1=2,圖變成
然后發現,找不到增廣路了,程序結束不用作圖了,我們發現
再找增廣路的過程中3→2這條邊正向一次,反向一次,相當於流量為0.
這就是反向邊的作用。
另外提供一種小技巧,使用鄰接表建圖的話,可以邊的編號從2開始建,我們知道
2^1=3,3^1=2……
這樣的話我們可以通過異或得到反向邊的編號(記得建完正向邊,緊接着就建反向邊),具體看代碼
時間復雜度為O(nm2)至於為什么,本人很菜不會,另外,一般時間復雜度是遠遠達不到這個值的。
代碼(EK)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define LL long long using namespace std; const int inf=1<<29; const int N=207; const int M=5e3+7; int n,m,s,t,cnt=1;//從編號2開始建邊 LL maxf;//最大流 int head[N],vis[N],pre[N]; LL incf[N]; LL v[N][N]; struct edge{ int v,nxt; LL w; }e[M<<1];//因為要建反向邊,所以開二倍空間 void add_edge(int u,int v,LL w){//存邊 cnt++; e[cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt; } bool bfs(){ memset(vis,0,sizeof(vis)); queue<int>q; q.push(s); vis[s]=1; incf[s]=inf; while(q.size()){ int now=q.front();q.pop(); for(int i=head[now];i;i=e[i].nxt){ if(e[i].w==0||vis[e[i].v])continue; int to=e[i].v; incf[to]=min(incf[now],e[i].w);//記錄路徑最小邊的流量 pre[to]=i;//記錄路徑邊的編號 q.push(to); vis[to]=1; if(to==t)return 1; } } return 0; } void up(){ int x=t; while(x!=s){ int i=pre[x]; e[i].w-=incf[t]; e[i^1].w+=incf[t];//反向邊加上正向邊減少的流量 x=e[i^1].v; } maxf+=incf[t]; } inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();} return x * f; } int main(){ n = read(); m = read(); s = read(); t = read(); for(int i=1;i<=m;i++){ int x,y,z; x = read(); y = read(); z = read(); v[x][y] += z; } for(int i = 1;i <= n;i ++) for(int j = 1;j <= n;j ++) if(v[i][j]) add_edge(i,j,v[i][j]),add_edge(j,i,0); while(bfs())up(); cout<<maxf<<endl; }//可能跟你們碼風不同,╮(╯▽╰)╭
Dinic
會了EK還學dinic有什么用呢,有用,我們來分析下下面這張圖不小心把顏色改了下,懶得再做一張。
如果你運氣不好的話像bmf一樣,若你每次找到的增廣路都經過了2→3或3→2這條邊的話你又涼了
所以這時候就用到了我們的Dinic算法。
Dinic 算法 的過程是這樣的:每次增廣前,我們先用 BFS 來將圖分層。設源點的層數為1 ,
那么一個點的層數便是它離源點的最近距離。
層次用數組dep表示。分層圖對於每條邊滿足dep[v]=dep[u]+1。
我們思考,EK算法每輪可能會遍歷整個圖,但只找出一條增廣路,屬於單路增廣。
而Dinic屬於多路增廣,時間復雜度O(n2m)
求解思路:
1.bfs求出節點的層次,構建分層圖
2.對於求出的分層圖,用dfs進行多路增廣,由於本人菜,講的不是很明白,我們可以看代碼
3.當前弧優化 :cur[]數組的應用,如果一條邊已經被增廣過,那么它就沒有可能被增廣第二次。那么,我們下一次進行增廣的時候,
就可以不必再走那些已經被增廣過的邊。
#include<bits/stdc++.h> #define LL long long const int N=210; const int M=5e3+7; using namespace std; int n,m,s,t,cnt=1; LL max_flow; int dep[N],head[N],cur[N]; struct edge{ int v,w,nxt; }e[M<<1];//數組含義與上一篇EK含義一樣,cur[]數組是dinic的一個優化,下面會提到 void add_edge(int u,int v,int w){ cnt++; e[cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt; } int bfs(){//構建分層圖 for(int i=1;i<=n;i++) dep[i]=0;//每個節點層次初始化 queue<int>q; q.push(s); dep[s]=1;//源點初始化為1; while(q.size()){ int now=q.front(); q.pop(); for(int i=head[now];i;i=e[i].nxt){ int to=e[i].v,val=e[i].w; if(val&&(!dep[to])){//構建分層圖的時候要保證邊不為0,如果dep[]已經被更新就不用更新了 q.push(to); dep[to]=dep[now]+1; if(to==t)return 1;//如果到達匯點,進行dfs } } } return 0; } int dfs(int u,int flow){ if(u==t)return flow; int rest=flow,k;//rest表示當前這個節點最大允許通過流量 for(int i=cur[u];i&&rest;i=e[i].nxt){ cur[u]=i;//當前弧優化 int to=e[i].v,val=e[i].w; if(val&&(dep[to]==dep[u]+1)){ int k=dfs(to,min(rest,val));//尋找增廣路 if(!k)dep[to]=0;//如果在to之后的路徑找不到增廣路,踢出分層圖 e[i].w-=k;//回溯時更新邊權 e[i^1].w+=k; rest-=k;//當前節點最大允許通過流量減去這次通過的流量 } } return flow-rest; } int main(){ scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add_edge(x,y,z); add_edge(y,x,0); } int flow=0; while(bfs()){ for(int i=1;i<=n;i++){ cur[i]=head[i]; //當前弧初始化 } while(flow=dfs(s,1<<29))max_flow+=flow; } cout<<max_flow<<"\n"; }
最小割
最小割問題是指:給出一種有向圖(無向圖)和兩個點s,t以及圖中的邊的邊權,
求一個權值和最小的邊集,使得刪除這些邊之后是s,t不連通。這類問題,一般運用最
大流等於最小流定理,求出最大流來解決。證明自行百度百科
附上代碼
int bfs(){ for(int i=1;i<=2*n;i++)dep[i]=0; queue<int>q; q.push(s); dep[s]=1; while(q.size()){ int now=q.front(); q.pop(); for(int i=head[now];i;i=e[i].nxt){ int to=e[i].v,val=e[i].w; if(val&&!dep[to]){ q.push(to); dep[to]=dep[now]+1; if(to==t)return 1; } } } return 0; } int dfs(int u,int flow){ if(u==t)return flow; int rest=flow,k; for(int i=cur[u];i;i=e[i].nxt){ cur[u]=i; int to=e[i].v,val=e[i].w; if(val&&(dep[to]==dep[u]+1)){ k=dfs(to,min(val,rest)); if(!k)dep[to]=0; e[i].w-=k; e[i^1].w+=k; rest-=k; } } return flow-rest; }
費用流
費用流就是每條邊除了有容量限制外,還有一個給定的單位費用 w(x,y),
當流過(x,y)這條邊時,要花費 f(x,y)*w(x,y)的費用
一般求解的問題是最小費用最大流和最大費用最大流,
基於之前提到的Ek算法,把bfs改成spfa即可,
就是每次先增廣費用最小的流。。。
int spfa(){//最小費用最大流 for(int i=0;i<=n;i++)dis[i]=inf,vis[i]=0; queue<int>q; q.push(s); dis[s]=0; vis[s]=1; incf[s]=inf; while(q.size()){ int now=q.front(); vis[now]=0; q.pop(); for(int i=head[now];i;i=e[i].nxt){ int to=e[i].v,val=e[i].w; if(!val)continue; if(dis[to]>dis[now]+e[i].f){ dis[to]=dis[now]+e[i].f; incf[to]=min(incf[now],val); pre[to]=i; if(!vis[to]){ vis[to]=1; q.push(to); } } } } if(dis[t]==inf)return 0; return 1; } int up(){ int x=t; max_flow+=incf[t]; min_cost+=dis[t]*incf[t]; while(x!=s){ int i=pre[x]; e[i].w-=incf[t]; e[i^1].w+=incf[t]; x=e[i^1].v; } }
end......