转载自http://blog.csdn.net/clove_unique/article/details/54884437
定义
f(u,v)表示u->v这条边的实际流量
b(u,v)表示u->v这条边的流量下界
c(u,v)表示u->v这条边的流量上界
在一个无源汇的普通网络流图中,满足
- 0≤f(u,v)≤c(u,v)
- ∑f(u,i)=∑f(i,v)
分别称为流量限制条件和流量平衡条件
而在有上下界的网络流图中,由于多了流量下界b(u,v)的限制,满足
- b(u,v)≤f(u,v)≤c(u,v)
- ∑f(u,i)=∑f(i,v)
无源汇可行流
建图方法
将有上下界的网络流图转化成普通的网络流图
- 首先建立附加源点ss和附加汇点tt
- 对于原图中的边x->y,若限制为[b,c],那么连边x->y,流量为c-b
- 对于原图中的某一个点i,记d(i)为流入这个点的所有边的下界和减去流出这个点的所有边的下界和
- 若d(i)>0,那么连边ss->i,流量为d(i)
- 若d(i)<0,那么连边i->tt,流量为-d(i)
求解方法
在新图上跑ss到tt的最大流
若新图满流,那么一定存在一种可行流
此时,原图中每一条边的流量应为新图中对应的边的流量+这条边的流量下界
证明
在原图中,假设每条边的实际流量为f(u,v)=b(u,v)+g(u,v)
其中g(u,v)≤c(u,v)−b(u,v)
我们可以将原图中的边改为上界为c(u,v)−b(u,v)的边,变成一个普通的网络流图
经过以上的改造,g(u,v)实际上是新图中边u->v的实际流量,原图中的实际流量f(u,v)=b(u,v)+g(u,v)
但是如果在这个新图中直接求可行流的话是错误的
举个栗子
原图
按照上面的方法改造过的新图
这个图的可行流为1,还原成原图的实际流量为
很显然满足流量限制条件,但是不满足流量平衡条件
由于需要满足流量平衡条件
∑f(u,i)=∑f(i,v)
∑[b(u,i)+g(u,i)]=∑[b(i,v)+g(i,v)]
∑b(u,i)−∑b(i,v)=∑g(i,v)−∑g(u,i)
令d(i)=∑b(u,i)−∑b(i,v)
当d(i)>0时
∑g(i,v)=∑g(u,i)+d(i)
所以需要一条边流量为d(i)来补流
当d(i)<0时
∑g(u,i)=∑g(i,v)+[−d(i)]
所以需要一条边流量为−d(i)来分流
可以发现,添加的所有与附加源点或者附加汇点相连的边必须满流,原图才有可行流
证毕
例题ZOJ Problem Set - 2314Reactor Cooling
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define maxn 205 #define maxm 50000 struct Edge{ int next,to,w,fl; }edge[maxm]; int n,m,t,fi[maxn],flow[maxn],ans[maxm],depth[maxn],que[maxn],first,last; bool vis[maxn]; inline void add_edge(int u,int v,int w,int x){ edge[x].next=fi[u],fi[u]=x,edge[x].to=v,edge[x].w=w,edge[x].fl=0, edge[x^1].next=fi[v],fi[v]=x^1,edge[x^1].to=u,edge[x^1].w=edge[x^1].fl=0; } inline void init(){ memset(fi,0,sizeof(fi)),memset(flow,0,sizeof(flow)); } inline bool bfs(){//查找是否存在增广路 memset(vis,0,sizeof(vis)); depth[0]=1,first=last=0,que[last++]=0,vis[0]=1; while(first<last){ int x=que[first++],v; for(int i=fi[x];i;i=edge[i].next){ v=edge[i].to; if(!vis[v]&&edge[i].w)que[last++]=v,vis[v]=1,depth[v]=depth[x]+1;//注:流量为0的边不能算 } } return vis[t]; } int dfs(int u,int f){ if(u==t||!f)return f; int v,x,sum=0; for(int i=fi[u];i;i=edge[i].next){ v=edge[i].to; if(depth[v]==depth[u]+1){//增广路必须走向下一层 x=dfs(v,min(edge[i].w,f-sum)); sum+=x,edge[i].w-=x,edge[i^1].w+=x,edge[i].fl+=x,edge[i^1].fl-=x; } } return sum; } inline int dinic(){//dinic求最大流 int sum=0,x; while(bfs()){//将图分层 if(x=dfs(0,0x3f3f3f3f))sum+=x;//在分过层的图上进行dfs求增广路 } return sum; } inline void work(){ init(),scanf("%d%d",&n,&m),t=n+1; int u,v,w,se=(m<<1)+1,sum=0; for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&u,&v,ans+i,&w), add_edge(u,v,w-ans[i],i<<1),flow[u]+=ans[i],flow[v]-=ans[i]; } for(int i=1;i<=n;i++){ if(flow[i]>0)add_edge(i,t,flow[i],++++se),sum+=flow[i];//若flow(i)>0,那么连边i->t,流量为flow(i) if(flow[i]<0)add_edge(0,i,-flow[i],++++se);//若flow(i)<0,那么连边s->i,流量为-flow(i) } if(dinic()==sum){//若新图满流,那么一定存在一种可行流 printf("YES "); for(int i=1;i<=m;i++){ printf("%d ",ans[i]+edge[i<<1].fl);//每一条边的流量应为新图中对应的边的流量+这条边的流量下界 } } else printf("NO ");//否则,不存在可行流 } int main(){ int t;scanf("%d",&t); while(t--)work(),printf(" "); return 0; }
有源汇可行流
建图方法
在原图中添加一条边t->s,流量限制为[0,inf]
即让源点和汇点也满足流量平衡条件
这样就改造成了无源汇的网络流图
其余方法同上
求解方法
同 无源汇可行流
证明
同 无源汇可行流
例题poj2396
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> using namespace std; #define maxe 10005 #define maxn 505 struct Edge{ int next,to,fl,w; }edge[maxe]; int n,m,fi[maxn],se,flow[maxn],s,t,ss,tt,c,ma[205][25],mi[205][25],depth[maxn],que[maxn],first,last; bool vis[maxn]; inline void add_edge(int u,int v,int w){ edge[++se].next=fi[u],fi[u]=se,edge[se].to=v,edge[se].w=w,edge[se].fl=0, edge[++se].next=fi[v],fi[v]=se,edge[se].to=u,edge[se].w=edge[se].fl=0; } inline void init(){ se=1,s=n+m+1,t=n+m+2,tt=n+m+3,ss=0, memset(fi,0,sizeof(fi)),memset(flow,0,sizeof(flow)), memset(mi,0,sizeof(mi)),memset(ma,0x3f,sizeof(ma)); } inline bool bfs(){//查找是否存在增广路 memset(vis,0,sizeof(vis)); depth[0]=1,first=last=0,que[last++]=0,vis[0]=1; while(first<last){ int x=que[first++],v; for(int i=fi[x];i;i=edge[i].next){ v=edge[i].to; if(!vis[v]&&edge[i].w)que[last++]=v,vis[v]=1,depth[v]=depth[x]+1;//注:流量为0的边不能算 } } return vis[tt]; } int dfs(int u,int f){ if(u==tt||!f)return f; int v,x,sum=0; for(int i=fi[u];i;i=edge[i].next){ v=edge[i].to; if(depth[v]==depth[u]+1){//增广路必须走向下一层 x=dfs(v,min(edge[i].w,f-sum)); sum+=x,edge[i].w-=x,edge[i^1].w+=x,edge[i].fl+=x,edge[i^1].fl-=x; } } return sum; } inline int dinic(){//dinic求最大流 int sum=0,x; while(bfs()){//将图分层 if(x=dfs(0,0x3f3f3f3f))sum+=x;//在分过层的图上进行dfs求增广路 } return sum; } inline bool update(int x,int y,char s,int w){ if(s=='<'){ if(mi[x][y]>=w)return 0; ma[x][y]=min(w-1,ma[x][y]); } else if(s=='>'){ if(ma[x][y]<=w)return 0; mi[x][y]=max(w+1,mi[x][y]); } else{ if(ma[x][y]<w||mi[x][y]>w)return 0; mi[x][y]=ma[x][y]=w; } return 1; } inline bool build(){ for(int i=1;i<=n;i++)scanf("%d",flow+i),flow[s]-=flow[i]; for(int i=1;i<=m;i++)scanf("%d",flow+n+i),flow[t]+=flow[n+i],flow[n+i]=-flow[n+i]; scanf("%d",&c); int x,y,w;char s1;bool f=1; for(int i=0;i<c;i++){ cin>>x>>y>>s1>>w; if(x==0){ for(int j=1;j<=n;j++){ if(y==0) for(int k=1;k<=m;k++)f=update(j,k,s1,w); else f=update(j,y,s1,w); } } else{ if(y==0) for(int k=1;k<=m;k++)f=update(x,k,s1,w); else f=update(x,y,s1,w); } } return f&&flow[s]==-flow[t]; } inline void work(){ int sum=0; scanf("%d%d",&n,&m),init(); if(!build()){ printf("IMPOSSIBLE ");return; } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ add_edge(i,n+j,ma[i][j]-mi[i][j]),flow[i]-=mi[i][j],flow[n+j]+=mi[i][j]; } } for(int i=1;i<=n+m;i++){ if(flow[i]>0)add_edge(ss,i,flow[i]),sum+=flow[i];//若flow(i)>0,那么连边i->t,流量为flow(i) if(flow[i]<0)add_edge(i,tt,-flow[i]);//若flow(i)<0,那么连边s->i,流量为-flow(i) } if(dinic()==sum){//若新图满流,那么一定存在一种可行流 for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ printf("%d ",mi[i][j]+edge[((i-1)*m+j)<<1].fl);//每一条边的流量应为新图中对应的边的流量+这条边的流量下界 } printf(" "); } printf(" "); } else printf("IMPOSSIBLE ");//否则,不存在可行流 } int main(){ int t;scanf("%d",&t); while(t--)work(); return 0; }
有源汇最大流
建图方法
同有源汇可行流
求解方法
在新图上跑ss到tt的最大流
若新图满流,那么一定存在一种可行流
记此时∑f(s,i)=sum1
将t->s这条边拆掉,在新图上跑s到t的最大流
记此时∑f(s,i)=sum2
最终答案即为sum1+sum2
证明
添加附加源汇的作用:为了满足流量平衡条件,在新图中进行相应的补流或分流
只要连接附加源汇的边满流,新图中s->t的任意一种可行流都是原图的可行流
跑ss->tt的最大流了之后,相当于是使连接附加源汇的边满流,进而求出了一种可行流
再将t->s的边拆掉(使s->t变成一个有源汇的网络流图),跑s到t的最大流,加上跑出来的最大流即为原图中一种可行的最大流
题目
http://cogs.pro:8080/cogs/problem/problem.php?pid=12
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 105 #define maxe 20005 int n,low[maxn][maxn],high[maxn][maxn],fi[maxn],se=1,ans,s,t,que[maxn],first,last,flow[maxn],depth[maxn]; struct Edge{ int next,to,fl,w; }edge[maxe]; inline void add_edge(int u,int v,int w1,int w2){ edge[++se].next=fi[u],edge[se].fl=0,edge[se].to=v,edge[se].w=w1,fi[u]=se, edge[++se].next=fi[v],edge[se].fl=0,edge[se].to=u,edge[se].w=w2,fi[v]=se; } int dfs(int x,int fl){ if(x==t||!fl)return fl; int tmp=0,t; for(int i=fi[x];i;i=edge[i].next){ int v=edge[i].to; if(depth[v]==depth[x]+1){ t=dfs(v,min(fl-tmp,edge[i].w)); edge[i].w-=t,edge[i].fl+=t,edge[i^1].w+=t,edge[i^1].fl-=t,tmp+=t; } } return tmp; } inline bool bfs(){ first=last=0,memset(depth,0,sizeof(depth)),que[last++]=s,depth[s]=1; while(first<last){ int x=que[first++]; for(int i=fi[x];i;i=edge[i].next){ int v=edge[i].to; if(depth[v]||!edge[i].w)continue; depth[v]=depth[x]+1,que[last++]=v; } } return depth[t]?1:0; } inline void dinic(){//最大流 int x; while(bfs()){ while(x=dfs(s,0x3f3f3f3f))ans+=x; } } int main(){ freopen("maxflowb.in","r",stdin); freopen("maxflowb.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ scanf("%d%d",&low[i][j],&high[i][j]); flow[i]+=low[i][j],flow[j]-=low[i][j]; } } high[n][1]=0x3f3f3f3f;//在原图中添加一条边t->s,流量限制为[0,inf] for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ add_edge(i,j,high[i][j]-low[i][j],high[j][i]-low[j][i]); } } s=0,t=n+1; for(int i=1;i<=n;i++){ if(flow[i]>0)add_edge(i,t,flow[i],0); if(flow[i]<0)add_edge(s,i,-flow[i],0); } dinic();//对ss到tt求一遍最大流 #if 1//删去t->s这条边,统计∑f(s,i) ans=0,edge[(n-1)<<1].w=high[1][n]-low[1][n]; for(int i=fi[n];i;i=edge[i].next){ int v=edge[i].to; if(v!=1)ans-=edge[i].fl; } s=1,t=n; #endif #if 0//或者不删去t->s这条边,这时t->s这条边的反向边s->t流量为∑f(s,i) s=1,t=n,ans=0; #endif dinic();//在残存网络中跑一遍s到t的最大流 printf("%d",ans); return 0; }
有源汇最小流
建图方法
同 无源汇可行流
求解方法
求ss->tt最大流
连边t->s,inf
求ss->tt最大流
答案即为边t->s,inf的实际流量
证明
第一遍做的时候并无t->s这条边,所以s->t的流量已经尽力往其它边流了
加上t->s这条边后,流过这条边的都是些剩余的流不到其他边的流量,从而达到尽可能减少T->S这边上的流量的效果,即减小了最终答案。
题目poj3801
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> using namespace std; #define maxn 60 #define maxe 550 struct Edge{ int next,to,w,fl; }edge[maxe]; int n,m,s,t,ss,tt,fi[maxn],se,flow[maxn],depth[maxn],que[maxn],first,last; inline void add_edge(int u,int v,int w){ edge[++se].next=fi[u],edge[se].to=v,edge[se].w=w,fi[u]=se,edge[se].fl=w, edge[++se].next=fi[v],edge[se].to=u,edge[se].w=edge[se].fl=0,fi[v]=se; } inline int readint(){ register int res = 0; register char tmp = getchar(); while(!isgraph(tmp))tmp=getchar(); if(tmp=='-')return t; if(tmp=='+')return s; while(isgraph(tmp))res=((res+(res)<<2)<<1)+(tmp^0x30),tmp=getchar(); return res; } inline void init(){ s=0,t=n+1,ss=t+1,tt=ss+1,se=1,memset(fi,0,sizeof(fi)),memset(flow,0,sizeof(flow)); } inline bool bfs(){ memset(depth,0,sizeof(depth)),first=last=0,que[last++]=ss,depth[ss]=1; while(first<last){ int x=que[first++]; for(int i=fi[x];i;i=edge[i].next){ int v=edge[i].to; if(depth[v]==0&&edge[i].w){ depth[v]=depth[x]+1,que[last++]=v; } } } return depth[tt]; } int dfs(int x,int fl){ if(x==tt||!fl)return fl; int tmp=0,t; for(int i=fi[x];i;i=edge[i].next){ int v=edge[i].to; if(depth[v]==depth[x]+1){ t=dfs(v,min(fl-tmp,edge[i].w)); edge[i].w-=t,edge[i^1].w+=t,tmp+=t; } } return tmp; } inline int dinic(){ int tot=0,x; while(bfs()){ while(x=dfs(ss,0x3f3f3f))tot+=x; } return tot; } inline void work(){ init(); for(int i=0;i<m;i++){ int u=readint(),v=readint(),w=readint(); add_edge(u,v,0x3f3f3f),flow[u]-=w,flow[v]+=w; } int sum=0,f1; for(int i=s;i<=t;i++){ if(flow[i]>0)add_edge(ss,i,flow[i]),sum+=flow[i]; if(flow[i]<0)add_edge(i,tt,-flow[i]); } f1=dinic();//先求一遍ss->tt最大流 add_edge(t,s,0x3f3f3f);//连边t->s,inf if(sum!=f1+dinic()){//求ss->tt最大流 printf("impossible ");return; } printf("%d ",edge[se-1].fl-edge[se-1].w);//答案即为边t->s,inf的实际流量 } int main(){ while(~scanf("%d%d",&n,&m)&&n|m)work(); return 0; }
有源汇有上下界费用流
建图方法
将有上下界的网络流图转化成普通的网络流图
- 首先建立附加源点ss和附加汇点tt
- 对于原图中的边x->y,若限制为[b,c],费用为cost,那么连边x->y,流量为c-b,费用为cost
- 对于原图中的某一个点i,记d(i)为流入这个点的所有边的下界和减去流出这个点的所有边的下界和
- 若d(i)>0,那么连边ss->i,流量为d(i),费用为0
- 若d(i)<0,那么连边i->tt,流量为-d(i),费用为0
- 连边t->s,流量为inf,费用为0
求解方法
跑ss->tt的最小费用最大流
答案即为(求出的费用+原图中边的下界*边的费用)
证明
注意:
有上下界的费用流指的是在满足流量限制条件和流量平衡条件的情况下的最小费用流
而不是在满足流量限制条件和流量平衡条件并且满足最大流的情况下的最小费用流
也就是说,有上下界的费用流只需要满足网络流的条件就可以了,而普通的费用流是满足一般条件并且满足是最大流的基础上的最小费用
证明同 有源汇的可行流
题目BZOJ3876: [Ahoi2014&Jsoi2014]支线剧情
#include<cstdio> #include<deque> #include<algorithm> #include<cstring> using namespace std; #define maxn 305 #define maxe 50005 struct Edge{ int next,to,w,fl; }edge[maxe]; int n,s,t,tt,fi[maxn],se=1,dis[maxn],flow[maxn],pre[maxn]; bool vis[maxn]; inline void add_edge(int u,int v,int w,int fl){ edge[++se].next=fi[u],edge[se].fl=fl,edge[se].to=v,edge[se].w=w,fi[u]=se, edge[++se].next=fi[v],edge[se].fl=0,edge[se].to=u,edge[se].w=-w,fi[v]=se; } bool SPFA(){ deque<int> q;int sum=0,tot=0; memset(dis,0x3f,sizeof(dis)),memset(vis,0,sizeof(vis)); q.push_back(s),vis[s]=1,dis[s]=0,flow[s]=0x3f3f3f3f,flow[t]=0; while(!q.empty()){ int u=q.front();q.pop_front(); if(dis[u]*tot>sum){//LLL优化 q.push_back(u);continue; } vis[u]=0,sum-=dis[u],tot--; for(int i=fi[u];i;i=edge[i].next){ int v=edge[i].to; if(edge[i].fl&&dis[v]>dis[u]+edge[i].w){ dis[v]=dis[u]+edge[i].w,flow[v]=min(flow[u],edge[i].fl),pre[v]=i;//pre[i]记录路径上到达点i的边的编号 if(vis[v])continue; vis[v]=1,sum+=dis[v],tot++; if(q.empty()||dis[v]>dis[q.front()])q.push_back(v);//SLF优化 else q.push_front(v); } } } if(flow[t]){//更新路径上的边的剩余流量 for(int i=pre[t];i;i=pre[edge[i^1].to]){ edge[i].fl-=flow[t],edge[i^1].fl+=flow[t]; } } return flow[t]; } int main(){ scanf("%d",&n),s=0,t=n+1; int k,v,w,ans=0,tt=n+2; tt=n+1;s=tt+1;t=s+1;//这里超级源汇点为s和t,tt为普通汇点 for(int i=1;i<=n;i++) { int t;scanf("%d",&t),flow[i]+=t; for(int j=1;j<=t;j++) { int y,z;scanf("%d%d",&y,&z),ans+=z;//初始为下界费用 add_edge(i,y,z,0x3f3f3f3f); flow[y]--; } add_edge(i,tt,0,0x3f3f3f3f); } add_edge(tt,1,0,0x3f3f3f3f); for(int i=1;i<=n;i++) { if(flow[i]>0)add_edge(i,t,0,flow[i]); if(flow[i]<0)add_edge(s,i,0,-flow[i]); } while(SPFA())ans+=dis[t]*flow[t];//求解费用流 printf("%d",ans); return 0; }