最大流
在圖網絡中,找到從源點到匯點的最大流量
基本思路:對任一一個可行流,求出其殘余網絡,在殘余網絡中,找到一條增廣路徑,確定該路徑的流量(min(c(i,j))f,每條邊的流量減去f,建立反向邊,流量為f。
因此我們可以用dfs找一條路徑,對路徑上流量進行修改,建立反向邊,形成新的殘余網絡。再進行一次dfs,多次dfs后我們可以得到最終結果。
時間復雜度分析:每次dfs最少增加1流量,網絡總流量為C,一次dfs復雜度為O(n+m),總的時間復雜度為C*(n+m)=C*n^2
一個修改策略是每次走最短的次數,使得可以從源點到匯點。因此可以將上述dfs改成bfs實現,該算法稱為EK算法,時間復雜度為nm^2
另一種策略是對網絡分層--Dinic算法
從源點到頂點相同步數的頂點歸為同一層,這一過程可以通過bfs實現。
緊接着,我們用dfs尋找增廣路徑(用棧實現可以保存路徑),要求路徑每次都是從前一層到下一層。那么只要最后達到匯點所在的層,本次dfs即可結束
EK算法實現:(POJ1273)
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; int G[300][300]; int prev[300];//每個結點的前驅節點 bool vis[300]; int n,m; const int inf=0x3f3f3f3f; int Augment() { deque<int>q; memset(prev,0,sizeof(prev)); memset(vis,0,sizeof(vis)); q.push_back(1); prev[1]=0; vis[1]=1; bool Find=0; int v; while(!q.empty()) //找到一條可行的路 { v=q.front(); q.pop_front(); for(int i=1;i<=m;i++) { if(G[v][i]>0&&!vis[i])//有容量可以走 { prev[i]=v; vis[i]=1; if(i==m) //達到最后一個節點 { Find=1; q.clear(); break; } else q.push_back(i); } } } if(!Find) return 0; int mi=inf; v=m; while(prev[v]) //從最后一個節點開始找前驅節點 { mi=min(mi,G[prev[v]][v]); //流量為路徑中所有流量中最小的 v=prev[v]; } //添加反向邊,同時修改路徑上每條邊的容量 v=m; cout<<"路徑: "; while(prev[v]) { cout<<prev[v]<<endl; G[prev[v]][v]-=mi; G[v][prev[v]]+=mi; v=prev[v]; } return mi; } int main() { while(~scanf("%d%d",&n,&m)) { int s,e,c; memset(G,0,sizeof(G)); for(int i=1;i<=n;i++) { scanf("%d%d%d",&s,&e,&c); G[s][e]+=c; } int maxFlow=0,aug; while(aug=Augment()) //每次增廣增加的流量 maxFlow+=aug; printf("%d\n",maxFlow); } }
Dinic算法實現:
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; int G[300][300]; bool vis[300]; int Layer[300]; int n,m; const int inf=0x3f3f3f3f; bool bfs()//用於網絡分層 { deque<int>q; memset(Layer,0xff,sizeof(Layer));//初始化為-1 Layer[1]=0; q.push_back(1); while(!q.empty()) { int v=q.front(); q.pop_front(); for(int i=1;i<=m;i++) { if(G[v][i]>0&&Layer[i]==-1) { Layer[i]=Layer[v]+1; if(i==m) //達到匯點 return true; else q.push_back(i); } } } return false; } int Dinic() { deque<int>q; int i,nMaxFlow=0; while(bfs())//能不能分層 { //用棧寫dfs,可以保存增廣路徑信息 q.push_back(1);//加入原點 memset(vis,0,sizeof(vis)); vis[1]=1; while(!q.empty()) { int nd=q.back(); if(nd==m)//如果是匯點 { //在棧中找容量最小的邊 int nMinc=inf; int nMinc_vs; //容量最小邊的起點 for(i=1;i<q.size();i++) { int vs=q[i-1]; int ve=q[i]; if(G[vs][ve]>0) { if(nMinc>G[vs][ve]) { nMinc=G[vs][ve]; //增廣路徑上最小的流量 nMinc_vs=vs; } } } //增廣,改圖 nMaxFlow+=nMinc; for(i=1;i<q.size();i++) { int vs=q[i-1]; int ve=q[i]; G[vs][ve]-=nMinc; //修改邊容量 G[ve][vs]+=nMinc; //添加反向邊 } //退棧使nMinc_vs成為棧頂,以便繼續dfs while(!q.empty()&&q.back()!=nMinc_vs) { vis[q.back()]=0; q.pop_back(); } } else { for(i=1;i<=m;i++) { if(G[nd][i]>0&&!vis[i]&&Layer[i]==Layer[nd]+1) { vis[i]=1; q.push_back(i); break; } } if(i>m) //找不到下一個點,回溯 q.pop_back(); } } } return nMaxFlow; } int main() { while(~scanf("%d%d",&n,&m)) { int s,e,c; memset(G,0,sizeof(G)); for(int i=1;i<=n;i++) { scanf("%d%d%d",&s,&e,&c); G[s][e]+=c; } cout<<Dinic()<<endl; } }
最小費用最大流
問題描述:在所有最大流集合中,求出費用最小的路徑
最小費用流:在所有流中,代價最小的流
解題思路:
1 求從出發點到匯點的最小費用通路u(s,t)
2 對該通路分配最大可行的流量:f=min(c(i,j)),並讓通路上所有邊的流量減少f。這時,對於通路上的飽和邊,費用應改為正無窮
3 做該通路的反向邊(j,i),令c(j,i)=f, d(j,i)=-d(i,j)
4 在這樣構成的網絡中,重復1,2,3.直到找不到從源點到匯點的最小費用通路為止。
反復使用spfa算法做從源點到匯點的最短路增廣操作。
NOI 志願者招募
//OJ上的題目也可以先轉化成對偶問題再進行求解 #include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int maxn=1003,maxm=10002*4; const int inf=0x7fffffff; struct edge{ edge *next,*op; int t,c,v; //容量,代價 }ES[maxm],*V[maxn]; int demond[maxn],sp[maxn],prev[maxn]; edge *path[maxn]; int N,M,S,T,EC=-1; inline void addedge(int a,int b,int v,int c=inf) { edge e1={V[a],0,b,c,v},e2={V[b],0,a,0,-v}; ES[++EC]=e1;V[a]=&ES[EC]; ES[++EC]=e2;V[b]=&ES[EC]; V[a]->op=V[b];V[b]->op=V[a]; } struct Queue{ int Q[maxn],QH,QL,Size; bool inq[maxn]; inline void ins(int v) //插入 { if(++QL>=maxn) QL=0; Q[QL]=v; inq[v]=true; Size++; } inline int pop() //彈出 { int r=Q[QH]; inq[r]=false; Size--; if(++QH>=maxn) //循環 QH=0; return r; } inline void reset() //重置 { memset(Q,0,sizeof(Q)); QH=Size=0; QL=-1; } }Q; void init() { int i,a,b,c; scanf("%d%d",&N,&M); for( i=1;i<=N;i++) scanf("%d",&demond[i]);//每天最少的志願者數量 for( i=1;i<=M;i++) { scanf("%d%d%d",&a,&b,&c); addedge(a,b+1,c); //對變量X添加邊 } S=0,T=N+2; for(i=1;i<=N+1;i++) { c=demond[i]-demond[i-1]; if(c>=0) addedge(S,i,0,c); //從源點到頂點連邊 else addedge(i,T,0,-c); //從頂點到匯點連邊 if(i>1) addedge(i,i-1,0); //對變量Y連邊 } } //用spfa根據邊上權值找一條最短路徑,保存這一路徑 bool spfa() { int u,v; for(u=S;u<=T;u++) sp[u]=inf; Q.reset(); Q.ins(S); sp[S]=0; prev[S]=-1; while(Q.Size) { u=Q.pop(); for(edge *k=V[u];k;k=k->next) { v=k->t; if(k->c>0&&sp[u]+k->v<sp[v]) { sp[v]=sp[u]+k->v; prev[v]=u; path[v]=k; if(!Q.inq[v]) Q.ins(v); } } } return sp[T]!=inf; } int argument() { int i,delta=inf,flow=0; edge *e; for(i=T;prev[i]!=-1;i=prev[i]) //路徑上的最小流量 { e=path[i]; if(e->c<delta) delta=e->c; } for(i=T;prev[i]!=-1;i=prev[i]) { e=path[i]; e->c-=delta;e->op->c+=delta; //容量減少,建立反向邊 flow+=e->v*delta; //計算代價 } return flow; } int maxcostflow() { int ans=0; while(spfa()) { ans+=argument();//增廣操作 } return ans; } int main() { init();//建圖 printf("%d\n",maxcostflow()); return 0; }