题外话
这是一个风和日丽,月朗风清的上午。
蒟蒻坐在机房里敲着代码,听着远处教学楼传来的开考哨,感觉心情一阵愉悦。
于是她决定学一下有上下界的网络流。
loj是一个好oj,你可以找到下面三种模型的模板题(但是本蒟蒻附的代码并不是loj上板子题的代码,因为在本蒟蒻发现loj上有板子题的时候,已经快到中午吃饭的时间了)
无源汇上下界可行流
分析
这类问题是这样的:给你一个网络,每条边i上的流量都必须满足
lowi≤flowi≤maxi
,问流量是否可以在其中循环流动?
首先我们让所有边的流量都为
low
,当然,这样子流量进出不平衡。
对于每一个点,我们记:“囤积其上”的流量为du。如果在当前网络上,这个点流出的流量大于流入的流量,du为正值。这个点流出的流量小于流入的流量,du为负值。
现在我们要继续往这张图里“灌水”,使得所有点进出平衡。也就是说,如果一个点的du为正值,就需要额外多流出du这么多的流量。反之,要额外流入这么多流量。
然而我们好像不能从算法上达成寻找进出不平衡的流的效果…
那么便从建图上搞吧!我们可以建立一个虚拟源点ss和一个虚拟汇点tt。对于一个du为正值的点i,我们可以连一条边
(ss,i,du)
,为其灌入du的流量好使其流出这么多。否则,连边
(i,tt,−du)
可以发现,如果原图能够构建一个合法流量网络,则此时跑完网络流后,所有与虚拟源点或虚拟汇点连接的边都应该满流。
代码
zoj2314
题目大意:有一个核反应堆,每条边流量的限制为[l,r],求是否可以循环流动。如果可以,输出每条边的流量。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=205,M=205*205*2+205,inf=0x3f3f3f3f;
int T,ss,tt,n,m,tot;
int h[N],ne[M],to[M],flow[M],du[N],lev[N],q[N],low[M];
void add(int x,int y,int z) {
to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {
if(x==tt) return liu;
int kl,sum=0;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
kl=dfs(to[i],min(flow[i],liu-sum));
sum+=kl,flow[i]-=kl,flow[i^1]+=kl;
if(sum==liu) return sum;
}
if(!sum) lev[x]=-1;
return sum;
}
int bfs() {
for(int i=ss;i<=tt;++i) lev[i]=0;
int x,he=1,ta=1; q[1]=ss,lev[ss]=1;
while(he<=ta) {
x=q[he],++he;
if(x==tt) return 1;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&!lev[to[i]])
lev[to[i]]=lev[x]+1,q[++ta]=to[i];
}
return 0;
}
int main()
{
int x,y,xj,sj,flag;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
tot=1,ss=0,tt=n+1;
memset(h,0,sizeof(h)),memset(du,0,sizeof(du));
for(int i=1;i<=m;++i) {
scanf("%d%d%d%d",&x,&y,&xj,&sj);
add(x,y,sj-xj),du[x]-=xj,du[y]+=xj,low[i]=xj;
}
for(int i=1;i<=n;++i)
if(du[i]>0) add(ss,i,du[i]);
else if(du[i]<0) add(i,tt,-du[i]);
while(bfs()) dfs(ss,inf);
flag=0;
for(int i=h[ss];i;i=ne[i]) if(flow[i]) {flag=1;break;}
if(flag) puts("NO");
else {
puts("YES");
for(int i=1;i<=m;++i) printf("%d\n",low[i]+flow[i*2+1]);
}
if(T) putchar('\n');
}
return 0;
}
有源汇上下界最大流
例题: zoj3229
题目大意:
大jay形是一个哲♂学日报的主编,他的任务是每天拍摄一些可爱的女孩子们并把照片刊登在日报上。他的日报要办n天,一共有m个可爱的女孩子。
大jay形每天可以拍
di
张照片,当天有
ci
个可爱的女孩子有空让他拍摄,但是他拍某个女孩子的照片数必须在区间
[lj,rj]
间。在日报办完后,对于某个女孩子j,大jay形刊登的她的照片数要大于等于
gj
,求大jay形是否可以达成任务,如果可以,最多可以拍多少张照♂片。
题目分析:
建模应该很直观,每天建一个点,每个女孩子建一个点。s向每天连一条流量为
[0,di]
的边,每个女孩子向汇点t连一条流量为
[gj,inf]
的边,然后每一天的点向当天可以拍摄的女孩子连一条流量为
[lj,rj]
的边。
现在开始检测是否有合法方案。由于源点和汇点并不满足流量进出平衡,所以似乎很难向上一题那样判断可行性。所以我们做一个操作:从t向s连一条流量为inf的额外边,这样就可以进出平衡了。然后仿照无源汇的方法搞一通。
最后,我们去掉虚拟源汇,再跑一次最大流,得到在满足下界情况下尽可能填满流量网络的最大流。
另外,此题的评测挂了2年了,所以本蒟蒻并不知道自己的代码对不对,先贴着吧。如果你想知道自己的代码对不对,可以去试一试loj上的板子题。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1400,M=200005,inf=0x3f3f3f3f;
int n,m,tot,ans;
int h[N],ne[M],to[M],flow[M],du[N],lev[N],q[N],low[M],can[N];
void add(int x,int y,int z) {
to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu,int t) {
if(x==t) return liu;
int kl,sum=0;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
kl=dfs(to[i],min(flow[i],liu-sum),t);
flow[i]-=kl,flow[i^1]+=kl,sum+=kl;
if(sum==liu) return sum;
}
if(!sum) lev[x]=-1;
return sum;
}
int bfs(int s,int t) {
for(int i=0;i<=n+m+3;++i) lev[i]=0;
int he=1,ta=1,x; q[1]=s,lev[s]=1;
while(he<=ta) {
x=q[he],++he;
if(x==t) return 1;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&!lev[to[i]])
lev[to[i]]=lev[x]+1,q[++ta]=to[i];
}
return 0;
}
int check() {
int ss=n+m+2,tt=n+m+3;
for(int i=0;i<=n+m+1;++i)
if(du[i]>0) add(ss,i,du[i]);
else if(du[i]<0) add(i,tt,-du[i]);
while(bfs(ss,tt)) dfs(ss,inf,tt);
for(int i=h[ss];i;i=ne[i]) if(flow[i]) return 0;
return 1;
}
int main()
{
int x,num,e_js,r;
while(~scanf("%d%d",&n,&m)) {
tot=1,ans=0; int S=0,T=n+m+1;
memset(h,0,sizeof(h)),memset(du,0,sizeof(du));
for(int i=1;i<=m;++i) scanf("%d",&x),du[T]+=x,du[i+n]-=x;
e_js=0;
for(int i=1;i<=n;++i) {
scanf("%d%d",&num,&can[i]);
for(int j=1;j<=num;++j) {
scanf("%d%d%d",&x,&low[++e_js],&r);
add(i,x+n+1,r-low[e_js]);
du[x+n+1]+=low[e_js],du[i]-=low[e_js];
}
}
for(int i=1;i<=n;++i) add(S,i,can[i]);
for(int i=1;i<=m;++i) add(i+n,T,inf);
add(T,S,inf);
if(check()) {
while(bfs(S,T)) ans+=dfs(S,inf,T);//由于(T,S,inf)的存在,会回流,所以可以得到最大流?
printf("%d\n",ans);
for(int i=1;i<=e_js;++i)
printf("%d\n",low[i]+flow[i*2+1]);
}
else puts("-1");
puts("");
}
return 0;
}
有源汇上下界最小流
分析
问题模型在标题里应该已经很直观了desi
事实上,我们跑出来的可行流并不一定是最小流。所以,跑完可行流后要怎么办呢?
当然是——求T到S的最大流!
显然,根据网络流反向边的定义,这样子当然可以尽可能多地减少流量。而由于我们跑可行流的时候,用于确认可行(即与虚拟源汇相连的边)是游离于整张图之外的,所以不会导致不可行。
代码
bzoj2502 (这是一道权限题,没有权限的孩子可以选择loj上的板子题)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=205,M=4e6,inf=0x3f3f3f3f;
int h[N],ne[M],to[M],flow[M],lev[N],q[N],du[N];
int n,m,tot=1,ans;
void add(int x,int y,int z) {
to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
void del(int x) {for(int i=h[x];i;i=ne[i]) flow[i]=flow[i^1]=0;}
int dfs(int x,int liu,int t) {
if(x==t) return liu;
int kl,sum=0;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
kl=dfs(to[i],min(flow[i],liu-sum),t);
sum+=kl,flow[i]-=kl,flow[i^1]+=kl;
if(sum==liu) return sum;
}
if(!sum) lev[x]=-1;
return sum;
}
int bfs(int s,int t) {
for(int i=0;i<=n+3;++i) lev[i]=0;
int he=1,ta=1,x; q[1]=s,lev[s]=1;
while(he<=ta) {
x=q[he],++he;
if(x==t) return 1;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&!lev[to[i]])
lev[to[i]]=lev[x]+1,q[++ta]=to[i];
}
return 0;
}
int main()
{
int num,x,S,T,ss,tt;
scanf("%d",&n);S=0,T=n+1,ss=n+2,tt=n+3;
for(int i=1;i<=n;++i) {
scanf("%d",&num);
for(int j=1;j<=num;++j) {
scanf("%d",&x);
++du[x],--du[i],add(i,x,inf);
}
}
for(int i=1;i<=n;++i) {
add(S,i,inf),add(i,T,inf);
if(du[i]>0) add(ss,i,du[i]);
else if(du[i]<0) add(i,tt,-du[i]);
}
add(T,S,inf);
while(bfs(ss,tt)) dfs(ss,inf,tt);
ans=flow[tot];
del(ss),del(tt),flow[tot]=flow[tot-1]=0;
while(bfs(T,S)) ans-=dfs(T,inf,S);
printf("%d",ans);
return 0;
}