有m條管道,n個節點,1爲水源(源點),n爲終點(匯點),每條管道有水流量上限,問如何分配每條水管的流量才能使終點處接受到的水流量最大。php
最小割最大流定理:是指在一個網絡流中,可以從源點到達匯點的最大流量等於若是從網絡中移除就可以致使網絡流中斷的邊的集合的最小容量和。node
流:從源點開始,在匯點結束的路徑,有大小(即流量)。
容量網絡:初始輸入的各邊的容量。
流量網絡:計算中已有的水流量,即對匯點作出貢獻的流量。
殘量網絡:容量網絡-流量網絡(開始就是容量網絡)。
增廣路:在殘量網絡上創建的新的合法的能夠對匯點作出貢獻的一條流。ios
EK算法:尋找最短的新的增廣路,累積貢獻,直至找不到爲止。
實現:每次經過bfs尋找增廣路,每次加入一條新的邊,同時創建一條反向邊(便於讓後面的流合理分配地佔據先前的流的邊)算法
鄰接矩陣版本(更快):網絡
#include <cstdio> #include <iostream> #include <queue> using namespace std; typedef long long ll; const int maxn=205; const ll INF=1e17; ll cap[maxn][maxn],flow[maxn][maxn]; ll minr[maxn];//從起到到該店地最小殘量 ll fa[maxn];//流上靠近起點的父節點 ll n,m,maxf=0; ll EK(){ maxf=0; ll s=1,t=m; while(1){ fill(minr,minr+m+5,0); minr[s]=INF; queue<ll>q; q.push(s); while(!q.empty() ){ ll u=q.front(); q.pop(); for(ll v=1;v<=m;v++){ if(!minr[v] && cap[u][v]>flow[u][v]){//minr[]做vis標記 fa[v]=u; q.push(v); minr[v]=min(minr[u],cap[u][v]-flow[u][v]); } } if(minr[t]) break; } if(!minr[t]) break; for(ll v=t;v!=s;v=fa[v]){ flow[fa[v]][v]+=minr[t];//正向流量增長 flow[v][fa[v]]-=minr[t];//反向流量減小 } maxf+=minr[t]; } } int main(){ scanf("%lld%lld",&n,&m); ll x,y,c; for(int i=1;i<=n;i++){ scanf("%lld%lld%lld",&x,&y,&c); cap[x][y]+=c;//出現平行邊時容量相加 } EK(); printf("%lld\n",maxf); }
鄰接表版本(點過多時用鄰接表避免MLE):spa
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; typedef long long ll; const int maxn=1e4+5; const ll INF=1e17; struct edge{ ll from,to,cap,flow; edge(ll f=0,ll t=0,ll c=0,ll fl=0):from(f),to(t),cap(c),flow(fl){} }; vector<edge>ed; vector<ll>G[maxn];//存點i鏈接的邊的編號 ll minr[maxn];//最小殘餘流 ll fa[maxn];//存靠近源的邊的編號 ll cnt,n,m,maxf,s,t; void addedge(ll from,ll to,ll cap){ ed.push_back(edge(from,to,cap,0)); ed.push_back(edge(to,from,0,0));//反向邊容量爲0 cnt=ed.size(); G[from].push_back(cnt-2); G[to].push_back(cnt-1);//反向邊 } void EK(){ maxf=0; while(1){ fill(minr,minr+n+5,0); minr[s]=INF; queue<ll>q; q.push(s); while(!q.empty()){ ll u=q.front(); q.pop(); for(int i=0;i<G[u].size();i++){ edge &e=ed[G[u][i]]; if(!minr[e.to]&&e.cap>e.flow){ fa[e.to]=G[u][i]; q.push(e.to); minr[e.to]=min(minr[u],e.cap-e.flow); } } if(minr[t])break;//一旦找到一個增廣路當即跳出(縮時關鍵) } if(!minr[t]) break; for(ll u=t;u!=s;u=ed[fa[u]].from){ ed[fa[u]].flow+=minr[t]; ed[fa[u]^1].flow-=minr[t]; } maxf+=minr[t]; } } void init(){ ed.clear(); for(int i=0;i<=n;i++) G[i].clear(); } int main(){ scanf("%lld%lld%lld%lld",&n,&m,&s,&t); init(); ll x,y,w; for(int i=1;i<=m;i++){ scanf("%lld%lld%lld",&x,&y,&w); addedge(x,y,w); } EK(); printf("%d\n",maxf); }
給定一個邊權有向圖,問用最小的代價(邊權和最小)切斷一些邊,使得起點到終點的最短路變長。code
解法:
將全部最短路合併成新的圖,該圖的最小割(最大流)就是解。
最短路建圖的方法:枚舉每條邊,若該邊到起點與終點的距離和+該邊邊權=最短路,該邊就在最短路上。get
#include <cstring> #include <iostream> #include <cstdio> #include <cmath> #include <algorithm> #include <vector> #include <queue> using namespace std; typedef long long ll; const ll maxn=1e4+5; const ll INF=1e17; ll n,m; struct qnode{ ll v; ll c;//距原點距離 qnode(ll _v=0,ll _c=0):v(_v),c(_c){} bool operator <(const qnode &r)const{ return c>r.c;//大於小於反轉<=>將距離更短的點放上面 } }; struct Edge{ ll v,cost;//v表示相連的頂點 Edge(ll _v=0,ll _cost=0):v(_v),cost(_cost){} }; vector<Edge>E[maxn]; bool vis[maxn]; ll dis1[maxn],dis2[maxn]; ll a[maxn],b[maxn],w[maxn]; void dijkstra(ll start,ll dis[]){ fill(dis,dis+n+5,INF); priority_queue<qnode>q;//存點 while(!q.empty() ) q.pop(); dis[start]=0; q.push(qnode(start,0)); qnode tmp; while(!q.empty() ){ tmp=q.top(); q.pop(); ll u=tmp.v ; if(vis[u])continue ; vis[u]=1;//將該點加入集合 for(ll i=0;i<E[u].size() ;i++){//遍歷和u相連的邊 ll v=E[u][i].v;//v是和u相連的點 ll cost=E[u][i].cost; if(!vis[v]&&dis[v]>dis[u]+cost){ dis[v]=dis[u]+cost; q.push(qnode(v,dis[v]));//用新的數據(更小的c)代替舊數據 } } } } void addedge1(ll u,ll v,ll w){ E[u].push_back(Edge(v,w)); } struct edge{ ll from,to,cap,flow; edge(ll f=0,ll t=0,ll c=0,ll fl=0):from(f),to(t),cap(c),flow(fl){} }; vector<edge>ed; vector<ll>G[maxn];//存點i鏈接的邊的編號 ll minr[maxn];//最小殘餘流 ll fa[maxn];//存靠近源的邊的編號 ll cnt,maxf,s,t; void addedge2(ll from,ll to,ll cap){ ed.push_back(edge(from,to,cap,0)); ed.push_back(edge(to,from,0,0));//反向邊容量爲0 cnt=ed.size(); G[from].push_back(cnt-2); G[to].push_back(cnt-1);//反向邊 } void EK(){ maxf=0; while(1){ fill(minr,minr+n+5,0); minr[s]=INF; queue<ll>q; q.push(s); while(!q.empty()){ ll u=q.front(); q.pop(); for(int i=0;i<G[u].size();i++){ edge &e=ed[G[u][i]]; if(!minr[e.to]&&e.cap>e.flow){ fa[e.to]=G[u][i]; q.push(e.to); minr[e.to]=min(minr[u],e.cap-e.flow); } } if(minr[t])break;//一旦找到一個增廣路當即跳出(縮時關鍵) } if(!minr[t]) break; for(ll u=t;u!=s;u=ed[fa[u]].from){ ed[fa[u]].flow+=minr[t]; ed[fa[u]^1].flow-=minr[t]; } maxf+=minr[t]; } } void init(){ for(ll i=0;i<=n;i++) E[i].clear(); fill(vis,vis+n+5,0); } void init2(){ ed.clear(); for(int i=0;i<=n;i++) G[i].clear(); } int main(){ ll T; scanf("%lld",&T); while(T--){ init(); scanf("%lld%lld",&n,&m); for(ll i=1;i<=m;i++){ scanf("%lld%lld%lld",&a[i],&b[i],&w[i]); addedge1(a[i],b[i],w[i]); } dijkstra(1,dis1); init(); for(ll i=1;i<=m;i++){ addedge1(b[i],a[i],w[i]); } dijkstra(n,dis2); ll mosts=dis1[n]; if(mosts==1e17){ printf("0\n"); continue; } init2(); for(ll i=1;i<=m;i++){ if(dis1[a[i]]+dis2[b[i]]+w[i]==mosts){ addedge2(a[i],b[i],w[i]); } } s=1;t=n; EK(); printf("%lld\n",maxf); } return 0; }