Luogu P3376
最大流是網絡流模型的一個基礎問題。
網絡流模型就是一種特殊的有向圖。
概念:算法
對於網絡流模型\(G=(V,E)\)(\(V\)爲點集,\(E\)爲邊集)有以下性質:網絡
最大流問題,用通俗的方式解釋就是從源點S到匯點T輸送流量,詢問最多有多少流量能輸送到匯點。
對於這樣的問題,咱們引入一些新概念:函數
\(Ford-Fulkerson\)方法:每一次尋找一條增廣路徑。根據木桶原理,該增廣路的最大流量\(f_{max}<=min(c(x,y)-f(x,y))\)。據此從源點發送流量至匯點並修改路徑上全部弧的殘量,直到沒法找到增廣路爲止。
\(Edmons-Karp\)算法:基於\(Ford-Fulkerson\)方法的一種算法,核心就是利用\(BFS\)搜索源點到匯點的最短增廣路,根據\(Ford-Fulkerson\)方法修改殘量網絡。複雜度最壞是\(O(nm^2)\)
因此其實咱們在求最大流相關問題時,其實只利用到了殘量網絡,流量和容量通常並不須要記錄。
關鍵點:有時候在求最大流時咱們可能須要縮減一條邊的流量,因此咱們引入了反向邊。當咱們選用了一條反向邊時,至關於縮減正向邊的流量。很容易發現反向邊的殘量等於正向邊的流量(最多剛好抵消正向流量)。
這樣就能保證算法的正確性。spa
#include<cstdio> #include<algorithm> #include<queue> #include<cstring> using namespace std; struct data { int to,next,val; }e[2*100005]; int cnt,head[10005],prep[10005],pree[10005],flow[10005],ans; queue<int> que; int n,m,s,t,u,v,w; void add(int u,int v,int w) { e[++cnt].to=v; e[cnt].next=head[u]; head[u]=cnt; e[cnt].val=w; } int bfs(int s,int t) { while (!que.empty()) que.pop(); flow[s]=0x3f3f3f3f;//flow記錄的是在增廣路上通過該點的流量 que.push(s); for (int i=1;i<=n;i++) { prep[i]=-1;//用於記錄前驅節點 pree[i]=0;//用於記錄前驅邊的編號 } prep[s]=0; while (!que.empty()) { int now=que.front(); que.pop(); if (now==t) break; for (int i=head[now];i;i=e[i].next) { if (e[i].val>0&&prep[e[i].to]==-1) { que.push(e[i].to); flow[e[i].to]=min(flow[now],e[i].val); pree[e[i].to]=i; prep[e[i].to]=now; } } } if (prep[t]!=-1) return flow[t]; else return -1; } void EK(int s,int t) { int delta=bfs(s,t);//尋找最短增廣路的最大流量 while (delta!=-1) { ans+=delta; for (int j=t;j;j=prep[j]) { e[pree[j]].val-=delta; e[pree[j]^1].val+=delta; //鏈式前向星存邊從編號2開始存儲能夠經過異或1快速取得反向邊的編號。 } delta=bfs(s,t); } } int main() { scanf("%d%d%d%d",&n,&m,&s,&t); cnt=1; for (int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); add(v,u,0); add(u,v,w); //加入正反邊 } EK(s,t); printf("%d",ans); return 0; }