最近几天学习了上下界网络流,感觉不太懂,但至少学会了如何建图。
总的来说,分为以下三个问题
- 无源汇最大流
- 有源汇最大流
- 有源汇最小流
-
一、无源汇最大流
大致的做法是这样的:
首先构成一个附加网络:将图的下界分离到一个附加源和汇中,而上界则变为原弧的上界减去下界的差。
再在附加源汇上跑一次最大流即可。记录每个点的入流下界和减去出流下界和,当下界和>0时怎连入一条源s到这个点的边,上界为这个下界和,下界为0;当下界和#include<bits/stdc++.h> using namespace std; #define maxn 500 #define oo ~0u>>1 #define maxm 80810 int S,T,cap[maxm],d[maxn],cur[maxn],gap[maxn],p[maxn]; int N,ihead[maxn],inext[maxm],to[maxm],first[maxm],cnt; int dn[maxm],du[maxn],from[maxm]; int isap(){ int flow=0,u,v,i,f=oo; memset(d,0,sizeof(d)); memset(gap,0,sizeof(gap)); for(i=1;i<=N;i++) {cur[i]=ihead[i];} u=S;gap[0]=N; while(d[S]<N){ for(i=cur[u];i;i=inext[i]){ if(cap[i]&&d[u]==d[to[i]]+1) break;} if(i){ cur[u]=i,v=to[i],f=min(f,cap[i]); p[v]=i,u=v; if(v==T){ for(;v!=S;v=from[p[v]]) cap[p[v]]-=f,cap[p[v]^1]+=f; u=S,flow+=f; f=oo; } } else{ if(!(--gap[d[u]])) break; d[u]=N; cur[u]=ihead[u]; for(i=cur[u];i;i=inext[i]) if(cap[i]&&d[u]>d[to[i]+1]) d[u]=d[to[i]]+1; gap[d[u]]++; if(u!=S) u=from[p[u]]; } } return flow; } void add_edge(int u,int v,int c){ inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c; inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0; } int main(){ int n,m,u,v,down,up,i,flag; while(cin>>n>>m){ S=n+1;T=n+2;N=n+2;cnt=1; memset(ihead,0,sizeof(ihead)); memset(du,0,sizeof(du)); memset(dn,0,sizeof(dn)); for(int i=1;i<=m;i++){ cin>>u>>v>>down>>up; add_edge(u,v,up-down); du[u]-=down;du[v]+=down; dn[i]=down; } for(int i=1;i<=n;i++){ if(du[i]>0) add_edge(S,i,du[i]); if(du[i]<0) add_edge(i,T,-du[i]); } isap(); flag=1; for(i=ihead[S];i;i=inext[i]) if(cap[i]){ flag=0; break; } if(!flag) cout<<"NO"<<'\n'; else{ cout<<"YES"<<'\n'; for(i=1;i<=m;i++) cout<<cap[(i<<1)+1]+dn[i]<<'\n'; } } }
二、有源汇最大流
大致的做法是这样的(一样的开头):
从汇t连一条弧到源s,上界为无限大,下界为0。
再设一个超级源ss和一个超级汇tt,按照无源汇上下界最大流的方法连边给ss和tt。
从ss跑最大流到tt,判断是否有弧满后,去掉超级源和汇及其边,再跑一次s-t的最大流(注意这里最大流绝不是t-s的反向弧,因为在第二次跑最大流的时候将这弧退掉了),最后根据题目要求输出即可。#include<bits/stdc++.h> using namespace std; #define CC(a, c) memset(a, c, sizeof(a)) const int maxn=1510, oo=100000000, maxm=151000; int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn]; int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt; int inout[maxn], down[400][1200], id[400][1200]; int E[maxm][2], e; int isap(int s, int t, int n) { int flow=0, i, u, f=oo; CC(gap, 0); CC(d, 0); for(i=0;i<=n;i++) cur[i]=ihead[i]; u=s; gap[0]=n; while(d[s]<n) { for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break; if(i) { cur[u]=p[to[i]]=i;u=to[i]; if(u==t) { for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]); for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f; flow+=f; } } else { if(!(--gap[d[u]]) ) break; d[u]=n; cur[u]=ihead[u]; for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1) d[u]=d[to[i]]+1; gap[d[u]]++; if(u!=s) u=from[p[u]]; } } return flow; } void add(int u, int v, int c){ inext[++cnt]=ihead[u];ihead[u]=cnt; from[cnt]=u;to[cnt]=v;cap[cnt]=c; inext[++cnt]=ihead[v];ihead[v]=cnt; from[cnt]=v;to[cnt]=u;cap[cnt]=0; } void init() { CC(ihead,0);cnt=1;CC(down,0); CC(id,0);CC(inout,0);e=0; } int main(){ int n,m,i,g,d,t,l,r,sum,S,T; while(cin>>n>>m){ init(); S=n+m+1;T=n+m+2; for(i=1;i<=m;i++) { cin>>g; add(i+n,T,oo-g); inout[i+n]-=g;inout[T]+=g; } for(int i=1;i<=n;i++){ cin>>g>>d; add(S, i, d); while(g--) { cin>>t>>l>>r; inout[i]-=l;inout[t+1+n]+=l;add(i,t+1+n,r-l); down[i][t+1]+=l;id[i][t+1]=cnt;E[++e][0]=i;E[e][1]=t+1; } } add(T,S,oo); S=n+m+3;T=n+m+4; sum=0; for(i=1;i<=n+m+2;i++){ if(inout[i]>0) { sum+=inout[i]; add(S,i,inout[i]); } if(inout[i]<0) add(i,T,-inout[i]); } if(isap(S, T, T)!=sum) cout<<-1<<'\n'; else { ihead[S]=ihead[T]=0;S=n+m+1;T=n+m+2; cout<<isap(S,T,T+2)<<'\n'; for(i=1;i<=e;i++) cout<<down[E[i][0]][E[i][1]]+cap[id[E[i][0]][E[i][1]]]<<'\n'; } cout<<'\n'; } }
三、有源汇最小流
大致的做法是这样的:
先构造附加网络到超级源和超级汇中,然后跑超级源到超级汇的最大流,再连汇t到源s上界为无限大下界为0的弧,再跑一次超级源到超级汇的最大流即可,最小流就是t-s的反向弧。
总的来说大概就是这样了。
一些代码和资料放在了Server上了。
参考:《一种简易的方法求解流量有上下界的网络中网络流问题》周源
也有放在Server上,链接同上