最大流
在图网络中,找到从源点到汇点的最大流量
基本思路:对任一一个可行流,求出其残余网络,在残余网络中,找到一条增广路径,确定该路径的流量(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 ",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 ",maxcostflow()); return 0; }