上下界网络流
2016-03-01 21:49
495 查看
一:概述
一般的网络流模型中对边只有上界的限制,及流过该边的流量不超过上界。上下界网络流的不同之处在于,每条边还有一个下界,即流过该边的流量不少于下界。其实一般的网络流也可以看做下界为0。上下界网络流的模型可以分为以下几种类型:
1.无源汇的上下界可行流:没有源点和汇点,要求一组满足流量限制的可行流。
2.有源汇的上下界最大流:有源点和汇点,求满足流量限制的最大流。
3.有源汇的上下界最小流:有源点和汇点,求满足流量限制的最小流。
4.上下界费用流:每条边有费用,在上面3中类型的基础上使得总费用最小。
二:构图技巧
无论是哪种类型,首先要解决的是下界的限制。对于一条边u->v,下界为l,上界为r。我们可以将这条边变为上界为r-l,下界为0。相当于在可行流的基础上每条边的流量都减小了l。但是这样做了之后,流量却不守恒了。流入v的流量少了l,流出u的流量也少了l。这时我们设置超级源点S和超级汇点T,从S向v连一条流量为l的边,从u到T连一条流量为l的边,来平衡流量。我们称这样的边为附加边。
其实我更习惯这样链附加边:记录一个数组d,对于一条边u->v,下界为l,d[v]+=l,d[u]-=l。然后扫描所有点,如果d[i]>0说明i需要流入d[i]的流量,所以从S向i连一条流量为d[i]的边,如果d[i]<0说明i需要流出-d[i]的流量,所以从i向T连一条流量为-d[i]的边。这样边数会少一些,跑起来就快一些。
对于不同的类型还有不同的处理技巧:
1.无源汇的上下界可行流
按上面的方式建边后,跑一遍S到T的最大流。检查所有附加边是否满流,若满流则存在可行流,否则不存在。若存在可行流,一组可行解就是每条边流过的流量加上流量下界。
一道例题:zoj2314
题目大意:给n个点,及m根管子,每根管子可单向运输液体,每时每刻每根管子流进来的物质要等于流出去的物质,m条管子组成一个循环体,里面流躺物质。并且满足每根管子一定的流量限制,范围为[Li,Ri].即要满足每时刻流进来的不能超过Ri,同时最小不能低于Li。问是否可行,可行则输出一组可行解。
思路:按上面的方式建边直接跑就行了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #include<vector> using namespace std; #define N 21000 struct edge{ int x,d,next; }e[1000100]; int first ,tot,du ,down ,dis ,Q ,cur ; int z,p,q,x,y,n,m,s,t; void add(int x,int y,int z){ e[++tot].x=y; e[tot].next=first[x]; e[tot].d=z; first[x]=tot; } void init(){ scanf("%d%d",&n,&m); tot=1; memset(du,0,sizeof(du)); memset(first,0,sizeof(first)); s=n+1; t=n+2; for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&x,&y,&down[i],&z); add(x,y,z-down[i]); add(y,x,0); du[x]-=down[i]; du[y]+=down[i]; } for(int i=1;i<=n;i++) if(du[i]<0) add(i,t,-du[i]),add(t,i,0); else add(s,i,du[i]),add(i,s,0); } bool bfs(){ memset(dis,-1,sizeof(dis)); Q[p=q=1]=s; dis[s]=0; for(;p<=q;p++){ int x=Q[p]; for(int i=first[x];i;i=e[i].next) if(e[i].d&&dis[e[i].x]==-1){ dis[e[i].x]=dis[x]+1; Q[++q]=e[i].x; } } if(dis[t]==-1) return 0; return 1; } int dfs(int x,int y){ if(x==t) return y; int sum=0,tmp; for(int i=cur[x];i;i=e[i].next) if(e[i].d&&dis[e[i].x]==dis[x]+1){ tmp=dfs(e[i].x,min(e[i].d,y-sum)); sum+=tmp; e[i].d-=tmp; e[i^1].d+=tmp; if(e[i].d) cur[x]=i; if(sum==y) return sum; } if(sum==0) dis[x]=-1; return sum; } int dinic(){ int ans=0; while(bfs()){ for(int i=1;i<=t;i++) cur[i]=first[i]; ans+=dfs(s,1000000007); } for(int i=first[s];i;i=e[i].next) if(e[i].d) return puts("NO"),ans; puts("YES"); for(int i=2;i<=2*m;i+=2) printf("%d\n",e[i+1].d+down[i/2]); return ans; } int main(){ int T; scanf("%d",&T); while(T--){ init(); dinic(); putchar('\n'); } return 0; }
2.有源汇的上下界最大流
源点和汇点是不满足流量平衡的,不能直接套用上面的方法。我们可以先将汇点向源点连一条上界为正无穷,下界为0的边。将源点和汇点变为满足流量平衡的普通点,再按上面的方式建边后,跑一遍S到T的最大流。若附加边未满流则无解。跑完一边后,我们将每条边的下界满足了,但这还不是最大流。因为这是原图中还有流量可流。我们将附加边和汇点向源点连的边删去,再跑一次最大流。这次就是真正的最大流了。
例题:zoj3229
题目大意:一个人给m个人拍照,计划拍照n天,每一天最多个C个人拍照,每天拍照数不能超过D张,而且给每个人i拍照有数量限制[Li,Ri],对于每个人n天的拍照总和不能少于Gi,如果有解求屌丝最多能拍多少张照,并求每天给对应女神拍多少张照;否则输出-1。
解题思路:增设一源点s,汇点t,s到第i天连一条上界为Di下界为0的边,每个人到汇点连一条下界为Gi上界为无穷的边,对于每一天,当天到第i个人连一条[Li,Ri]的边。按上面的方法构图就行了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #include<vector> using namespace std; #define INF 1000000007 struct edge{ int x,data,next,id; }e[1000000]; int first[2000],dis[2000],du[2000],ans[100000]; int s,t,tot,ss,sum,num,tt,x,y,d,c,n,m; void add(int x,int y,int d,int id){ e[++tot].x=y; e[tot].data=d; e[tot].id=id; e[tot].next=first[x]; first[x]=tot; if(~tot&1) add(y,x,0,id); } bool bfs(){ int c[2000],p,q; memset(dis,-1,sizeof(dis)); dis[c[1]=s]=1; for(p=q=1;p<=q;p++) for(int i=first[c[p]];i;i=e[i].next) if(e[i].data&&dis[e[i].x]==-1){ dis[e[i].x]=dis[c[p]]+1; c[++q]=e[i].x; } return dis[t]!=-1; } int dfs(int x,int y){ if(x==t||y==0) return y; int tmp,rec=0; for(int i=first[x];i;i=e[i].next) if(e[i].data&&dis[e[i].x]==dis[x]+1){ tmp=dfs(e[i].x,min(e[i].data,y-rec)); rec+=tmp; e[i].data-=tmp; e[i^1].data+=tmp; if(rec==y) return rec; } if(rec==0) dis[x]=-1; return rec; } int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,INF); return ans; } void solve(){ ss=n+m+1; tt=n+m+2; for(int i=1;i<=m;i++){ scanf("%d",&x); du[i+n]-=x; du[tt]+=x; add(i+n,tt,INF-x,0); } for(int i=1;i<=n;i++){ scanf("%d%d",&c,&d); add(ss,i,d,0); for(int j=1;j<=c;j++){ scanf("%d%d%d",&d,&x,&y); du[i]-=x; du[n+d+1]+=x; ans[++num]=x; add(i,n+d+1,y-x,num); } } s=n+m+3; t=n+m+4; for(int i=1;i<=n+m+2;i++) if(du[i]>0) add(s,i,du[i],0); else add(i,t,-du[i],0); add(tt,ss,INF,0); dinic(); for(int i=first[s];i;i=e[i].next) if(e[i].data) {puts("-1"); return;} first[s]=first[t]=0; s=ss; t=tt; sum=dinic(); printf("%d\n",sum); for(int i=3;i<=tot;i+=2) ans[e[i].id]+=e[i].data; for(int i=1;i<=num;i++) printf("%d\n",ans[i]); } int main(){ while(~scanf("%d%d",&n,&m)){ memset(ans,0,sizeof(ans)); memset(first,0,sizeof(first)); memset(du,0,sizeof(du)); tot=1; num=0; solve(); puts(""); } return 0; }
有源汇的上下界最小流
和有源汇的上下界最大流的构图相同。但是跑完第一次最大流时的答案并不是最小流。应为他只是满足了网络的下界,而网络中可能存在环,这样部分的流量我们没有充分利用。比如下面这张图:这样求得的最小流为200,而实际的可行最小流解只需100。
所有我们先不加汇点到源点的流量为正无穷的边,跑一遍最大流。这是是将原图中的环流满。连上汇点到源点的流量为正无穷的边后再跑一次。若所有附加边满流,则第二次的答案即为最小流,否则无解。
例题:sgu176
题目大意:有一个加工生产的机器,起点为1终点为n,中间生产环节有货物加工数量限制,输出u v z c,描述一个加工环节,当c等于1时表示这个加工的环节必须对纽带上的货物全部加工(即上下界都为z),c等于0表示加工上界为z,下界为0,起点最少需要投放多少货物才能传送带正常工作。
解题思路:按上面的方式构图即可。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #include<vector> using namespace std; #define N 110 #define INF 1000000007 struct edge{ int x,next,data,id; }e[N*N]; int tot=1,first ,sum ,c,dis ,f[N*N],ans,n,m,x,y,z,s,t; queue<int>q; void add(int x,int y,int z,int id){ e[++tot].x=y; e[tot].data=z; e[tot].id=id; e[tot].next=first[x]; first[x]=tot; } bool bfs(){ memset(dis,-1,sizeof(dis)); q.push(s); dis[s]=1; while(!q.empty()){ x=q.front(); q.pop(); for(int i=first[x];i;i=e[i].next) if(e[i].data&&dis[e[i].x]==-1){ dis[e[i].x]=dis[x]+1; q.push(e[i].x); } } return dis[t]!=-1; } int dfs(int x,int y){ if(x==t||y==0) return y; int tmp,rec=0; for(int i=first[x];i;i=e[i].next) if(dis[e[i].x]==dis[x]+1&&e[i].data){ tmp=dfs(e[i].x,min(y-rec,e[i].data)); rec+=tmp; e[i].data-=tmp; e[i^1].data+=tmp; if(rec==y) return rec; } if(rec==0) dis[x]=-1; return rec; } int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,INF); return ans; } void work(){ for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&x,&y,&c,&z); if(z) sum[x]-=c,sum[y]+=c,f[i]=c; else add(x,y,c,i),add(y,x,0,i); } s=n+1; t=n+2; for(int i=1;i<=n;i++) if(sum[i]>0) add(s,i,sum[i],0),add(i,s,0,0); else add(i,t,-sum[i],0),add(t,i,0,0); dinic(); add(n,1,INF,0); add(1,n,0,0); ans=dinic(); for(int i=first[s];i;i=e[i].next) if(e[i].data){puts("Impossible");return;} printf("%d\n",ans); for(int i=3;i<=tot;i+=2) f[e[i].id]=e[i].data; for(int i=1;i<m;i++) printf("%d ",f[i]); printf("%d\n",f[m]); } int main(){ while(~scanf("%d%d",&n,&m)){ memset(sum,0,sizeof(sum)); memset(first,0,sizeof(first)); tot=1; work(); } return 0; }
4.上下界费用流
先认准是上面模型三种中的哪一种,按其构图方法构图。对于原图中的边,费用为该边的费用,对于附加边,费用为0。将最大流改为费用流即可。但是答案还要加上所有边的费用乘上该边的流量下界。应为我们算出来的费用是没有计算下界的费用的。例题:3876: [Ahoi2014]支线剧情
题目大意:一张有向无环图,每条边有费用。从任意点都可回到起点,无需费用。求由起点遍历所有边的最小费用。
解题思路:每条边必须经过一次,便将边的上界设为正无穷,下界设为1。任何点可以回到起点,便将所有点向起点连一条上界为正无穷,下界为0,费用为0的边。整个图变成了一个无源汇的图。按无源汇的可行流构图,跑费用流就可以了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #include<vector> using namespace std; #define N 400 #define INF 1000000007 int first ,dis ,c[N*N],pre ,du ; int p,q,n,s,t,k,x,y,tot=1,sum; bool v ; struct edge{ int from,to,data,cost,next; }e[N*N]; void add(int x,int y,int d,int c){ e[++tot].to=y; e[tot].from=x; e[tot].data=d; e[tot].cost=c; e[tot].next=first[x]; first[x]=tot; } bool spfa(){ memset(dis,63,sizeof(dis)); dis[c[1]=s]=0; v[s]=true; for(p=q=1;p<=q;v[c[p]]=false,p++) for(int i=first[c[p]];i;i=e[i].next) if(e[i].data&&dis[e[i].to]>dis[c[p]]+e[i].cost){ dis[e[i].to]=dis[c[p]]+e[i].cost; pre[e[i].to]=i; if(!v[e[i].to]) v[c[++q]=e[i].to]=true; } return dis[t]!=dis[0]; } int costflow(){ int ans=0,flow; while(spfa()){ flow=INF; for(int i=pre[t];i;i=pre[e[i].from]) flow=min(flow,e[i].data); for(int i=pre[t];i;i=pre[e[i].from]){ ans+=flow*e[i].cost; e[i].data-=flow; e[i^1].data+=flow; } } return ans; } int main(){ scanf("%d",&n); s=n+1; t=n+2; for(int i=1;i<=n;i++){ scanf("%d",&k); for(int j=1;j<=k;j++){ scanf("%d%d",&x,&y); add(i,x,INF,y); add(x,i,0,-y); du[x]++; du[i]--; sum+=y; } if(i!=1) add(i,1,INF,0),add(1,i,0,0); } for(int i=1;i<=n;i++) if(du[i]>0) add(s,i,du[i],0),add(i,s,0,0); else add(i,t,-du[i],0),add(t,i,0,0); printf("%d\n",costflow()+sum); return 0; }
相关文章推荐
- 深度神经网络结构以及Pre-Training的理解
- TCP与UDP的区别
- HTTP中的Host字段
- iOS网络数据安全
- 浏览器XMLHttpRequest案例
- 网络编程Study
- HTTP状态码大全
- JAVA的包装类 http://blog.csdn.net/hjf19790118/article/details/7081925
- TCP/IP基础(二)
- Java泛型的类型擦除 http://blog.csdn.net/hust_is_lcd/article/details/7875386
- 卷积神经网络用于视觉识别Convolutional Neural Networks for Visual Recognition
- 使用AsyncTask异步加载类进行访问网络数据json的理解和用法
- 关于安装Httpd Web服务器
- Java泛型-类型擦除 http://blog.csdn.net/caihaijiang/article/details/6403349
- [国嵌攻略][091][TCP网络程序设计]
- meta标签中的http-equiv属性使用介绍
- 透过ATS缓存配置看如何判断HTTP资源是否可缓存方法论
- linux网络文件系统挂载配置
- 2055: 80人环游世界|有上下界的费用流
- Libcurl中配置openssl使其支持https