网络流总结
来源:互联网 发布:编辑图片大小的软件 编辑:程序博客网 时间:2024/06/06 03:30
1.最大流
有一个有向图,u到v的有向边表示水可以从u流向v
边上有容量,表示水最多流过去的量
有一个源点一个汇点,从源点这倒水,水通过流网络流向汇点,问这个流网络最多可以承受多少倒进去的水
这个问题就是最大流
在解决实际问题时主要还是建模比较考思维
有几个现成的例子比如二分图跑最大流,或者由最大流可以解决最小割而引出的最大密度子图这些
还有一个分支就是平面图最大流(狼抓兔子)考到了平面图的对偶图
这是暑假讲的了
贴一个模板
//总点数n+m+2,编号从0开始,源点0,汇点n+m+1struct node{ int v,w,next,op;}edge[360020];int head[360020],d[60000],vd[60000],n,m,flow,cnt;void addedge(int u,int v,int w){ edge[++cnt].v=v; edge[cnt].w=w; edge[cnt].op=cnt+1; edge[cnt].next=head[u]; head[u]=cnt; edge[++cnt].v=u; edge[cnt].w=0; edge[cnt].op=cnt-1; edge[cnt].next=head[v]; head[v]=cnt;}int aug(int u,int augco){ if(u==n+m+1)return augco; int augc=augco,mind=n+m+1; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].v; if(d[u]==d[v]+1&&edge[i].w>0) { int delta=min(augc,edge[i].w); delta=aug(v,delta); edge[i].w-=delta; edge[edge[i].op].w+=delta; augc-=delta; if(d[0]>=n+m+2)return augco-augc; if(augc==0)break; } if(edge[i].w>0)mind=min(mind,d[v]); } if(augco==augc) { vd[d[u]]--; if(vd[d[u]]==0)d[0]=n+m+2; else { d[u]=mind+1; vd[d[u]]++; } } return augco-augc;}void sap(){ vd[0]=n+m+2; while(d[0]<n+m+2)flow+=aug(0,999999999);}
2.费用流
还是上面那个问题,不过现在流网络不是你家开的,每用一条边流水,都要交钱,每单位的水对于不同的边交不同的钱,还是想流过去的水最多,而且要交的钱最少
这是最小费用最大流
建模的时候有个技巧就是拆点,把一个点拆成两个中间连一条边表示选这个东西的费用
写法好像挺多的样子。。学习了一下SPFA和zkw的费用流
实测zkw很多时候要快一些
贴两个写法的模板
POJ 2195
#include<iostream>#include<cstdio>#include<cstring>#include<queue>#include<vector>#define pii pair<int,int>using namespace std;vector<pii>H,M;struct node{ int v,next,cost,cap;}edge[100000];int n,m,cnt,src,des,ans,head[300],dis[300];bool vis[300],inq[300];char c;int abs(int x){ return x<0?-x:x;}int getd(pii x,pii y){ return abs(x.first-y.first)+abs(x.second-y.second);}void addedge(int u,int v,int cost,int cap){ edge[cnt].v=v; edge[cnt].cost=cost; edge[cnt].cap=cap; edge[cnt].next=head[u]; head[u]=cnt++;}bool spfa(){ queue<int>q; q.push(des); memset(dis,0x3f,sizeof dis); inq[des]=1; dis[des]=0; while(!q.empty()) { int u=q.front(); q.pop(); inq[u]=0; for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].v; if(edge[i^1].cap>0&&dis[v]>dis[u]+edge[i^1].cost) { dis[v]=dis[u]+edge[i^1].cost; if(!inq[v])q.push(v),inq[v]=1; } } } if(dis[src]==0x3f3f3f3f)return 0; return 1;}int aug(int u,int augco){ if(u==des) { ans+=dis[src]*augco; return augco; } int augc=augco,delta; for(int i=head[u];~i;i=edge[i].next) if(!vis[edge[i].v]&&edge[i].cap>0&&dis[edge[i].v]==dis[u]-edge[i].cost) { vis[edge[i].v]=1; delta=aug(edge[i].v,min(augc,edge[i].cap)); augc-=delta; edge[i].cap-=delta; edge[i^1].cap+=delta; } return augco-augc;}int main(){ while(scanf("%d%d",&n,&m)&&n&&m) { H.clear(); M.clear(); memset(inq,0,sizeof inq); memset(head,-1,sizeof head); cnt=ans=0; for(int i=1;i<=n;++i) { c=getchar(); for(int j=1;j<=m;++j) { c=getchar(); if(c=='H')H.push_back(pii(i,j)); else if(c=='m')M.push_back(pii(i,j)); } } int hsz=H.size(),msz=M.size(); for(int i=0;i<msz;++i) { addedge(1,i+2,0,1); addedge(i+2,1,0,0); for(int j=msz;j<hsz+msz;++j) { int d=getd(M[i],H[j-msz]); addedge(i+2,j+2,d,1); addedge(j+2,i+2,-d,0); } } for(int i=msz;i<hsz+msz;++i) { addedge(i+2,hsz+msz+2,0,1); addedge(hsz+msz+2,i+2,0,0); } src=1,des=hsz+msz+2; while(spfa()) { memset(vis,0,sizeof vis); vis[src]=1; aug(src,999999999); } printf("%d\n",ans); }}
#include<iostream>#include<cstdio>#include<cstring>#include<queue>#include<vector>#define pii pair<int,int>using namespace std;vector<pii>H,M;struct node{ int v,next,cost,cap;}edge[100000];int n,m,cnt,src,des,ans,head[300],dis[300];bool vis[300];char c;int abs(int x){ return x<0?-x:x;}int getd(pii x,pii y){ return abs(x.first-y.first)+abs(x.second-y.second);}void addedge(int u,int v,int cost,int cap){ edge[cnt].v=v; edge[cnt].cost=cost; edge[cnt].cap=cap; edge[cnt].next=head[u]; head[u]=cnt++;}bool label(){ int mi=999999999; for(int i=src;i<=des;++i) { if(vis[i]) { for(int j=head[i];~j;j=edge[j].next) if(!vis[edge[j].v]&&edge[j].cap>0) mi=min(mi,edge[j].cost+dis[edge[j].v]-dis[i]); } } if(mi==999999999)return 0; for(int i=src;i<=des;++i) if(vis[i])dis[i]+=mi; return 1;}int aug(int u,int augco){ if(u==des) { ans+=dis[src]*augco; return augco; } int augc=augco,delta; vis[u]=1; for(int i=head[u];~i&&augc>0;i=edge[i].next) if(edge[i].cap>0&&!vis[edge[i].v]&&edge[i].cost+dis[edge[i].v]==dis[u]) { delta=aug(edge[i].v,min(edge[i].cap,augc)); augc-=delta; edge[i].cap-=delta; edge[i^1].cap+=delta; } return augco-augc;}int main(){ while(scanf("%d%d",&n,&m)&&n&&m) { H.clear(); M.clear(); memset(dis,0,sizeof dis); memset(vis,0,sizeof vis); memset(head,-1,sizeof head); cnt=ans=0; for(int i=1;i<=n;++i) { c=getchar(); for(int j=1;j<=m;++j) { c=getchar(); if(c=='H')H.push_back(pii(i,j)); else if(c=='m')M.push_back(pii(i,j)); } } int hsz=H.size(),msz=M.size(); for(int i=0;i<msz;++i) { addedge(1,i+2,0,1); addedge(i+2,1,0,0); for(int j=msz;j<hsz+msz;++j) { int d=getd(M[i],H[j-msz]); addedge(i+2,j+2,d,1); addedge(j+2,i+2,-d,0); } } for(int i=msz;i<hsz+msz;++i) { addedge(i+2,hsz+msz+2,0,1); addedge(hsz+msz+2,i+2,0,0); } src=1,des=hsz+msz+2; do { do { memset(vis,0,sizeof vis); }while(aug(src,999999999)); }while(label()); printf("%d\n",ans); }}
然后上面说的是最小费用流
现在假设这个流网络是你家开的,别人来你这灌水,你收别人钱
你想找一个方案,让他灌的水最多并且给你的钱最多
这个是最大费用流
写法一样,费用存进去的时候记得取反
得到答案后记得还原
3.上下界网络流
有三个子问题:
a.无源汇点的可行流
网络流当中没有源点汇点,那么这一定是一个循环流,即水流绕圈流动
问存不存在这样的流
这个麻烦在有上下界,普通网络流只有上界而无下界,所以这驱使我们思考怎么消除下界影响
假设一个点下界流进去a,下界流出去b
如果a大于b,那么说明下界流进去很多,而只满足下界的话流出来不够,需要自由流(就是在上下界之间的流量)来补
如果a小于b,那么说明下界流进去很少,而只满足下界的话流出来就已经超了,需要自由流(就是在上下界之间的流量)来补
所以我们现在考虑的问题就转化成了自由流怎么补满每个点
这就变成了一个普通的最大流问题了。
总结一下,先新建源点汇点,对每个点,统计流入的下界和流出的下界之差,如果流入的小于流出来的,就从源点往这个点连边,容量是差值的相反数
如果流入的大于流出的,就从这个点连一条容量为差值的边到汇点去
对于原图中的点,如果有边相连那么容量是上界-下界
最后跑最大流,如果源点出去的每个边满流那么有可行流
贴一个模板SGU176
#include<iostream>#include<cstdio>#include<cstring>using namespace std;struct node{ int v,next,cap;}edge[100010];int n,m,cnt,flow,src,des,head[210],upl[50010],dwl[50010],io[210],d[210],vd[210];void addedge(int u,int v,int cap){ edge[cnt].v=v; edge[cnt].cap=cap; edge[cnt].next=head[u]; head[u]=cnt++;}int aug(int u,int augco){ if(u==n+1)return augco; int augc=augco,mind=n+1; for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].v; if(d[u]==d[v]+1&&edge[i].cap>0) { int delta=aug(v,min(augc,edge[i].cap)); edge[i].cap-=delta; augc-=delta; edge[i^1].cap+=delta; if(d[0]>=n+2)return augco-augc; if(augc==0)break; } if(edge[i].cap>0)mind=min(mind,d[v]); } if(augco==augc) { vd[d[u]]--; if(vd[d[u]]==0)d[0]=n+2; else { d[u]=mind+1; vd[d[u]]++; } } return augco-augc;}void sap(){ vd[0]=n+2; while(d[0]<n+2)flow+=aug(0,2147483640); for(int i=head[src];~i;i=edge[i].next) if(edge[i].cap) { puts("NO"); return; } puts("YES"); for(int i=0;i<m;++i) printf("%d\n",dwl[i]+edge[i*2+1].cap);}int main(){ memset(head,-1,sizeof head); scanf("%d%d",&n,&m); src=0,des=n+1; for(int i=0,u,v;i<m;++i) { scanf("%d%d%d%d",&u,&v,&dwl[i],&upl[i]); addedge(u,v,upl[i]-dwl[i]); addedge(v,u,0); io[u]-=dwl[i]; io[v]+=dwl[i]; } for(int i=1;i<=n;++i) { if(io[i]>0)addedge(src,i,io[i]),addedge(i,src,0); else if(io[i]<0)addedge(i,des,-io[i]),addedge(des,i,0); } sap();}
b.有源汇点的最大流
对于这个图,首先要考虑的是有没有可行流。于是想办法把有源汇点的图转化成没有源汇点的图,方法很简单就是从汇点连一条容量为无穷大的边到源点,说白了就是你在源点灌水,汇点的人收到水之后把收到的水搬过来给你,你又往源点里面倒
这样流网络就只有循环流了
于是对这个没有源汇点的图跑一个可行流,建图方法同上
如果可行,那么考虑现在这个图的残留网络,显然边的下界都被满足了,然而自由流只是找了一条可行流出来,不一定最优,于是还可以对自由流跑最大流以求出原图的最大流,方法是删掉刚才新加的源点汇点和汇点到源点的边,注意到现在这个图就是只表示自由流了,对这个图求个最大流,答案就是刚才的可行流+现在的最大流
当然二分也可以,从汇点往源点连一条下界为a,上界无穷大的边,说白了你给在汇点的人说了,收到的水必须多于a才能搬回来,然后对这个图做可行流,看看可行不,通过二分调节a的取值最后得到最大值就是答案
c.有源汇点的最小流
既然有下界那么自然可以求最小流
方法是对原图做可行流,不过这次不加汇点到源点那条边,跑个最大流,然后再加上汇点到源点那条边再跑一次最大流。答案就是第二次跑的最大流。
感性理解是最终解只能是加上边后,求的无源汇可行流,即T->S这边上的流量. 不改成无源汇的直接求的解是未必正确的
然后,因为第一遍做的时候并无这条边,所以S->T的流量在第一遍做的时候都已经尽力往其他边流了. 于是加上T->S这条边后,都是些剩余的流不到其他边的流量. 从而达到尽可能减少T->S这边上的流量的效果,即减小了最终答案.本质是延迟对T->S这条边的增流.
当然二分也可以,从汇点往源点连一条下界为0,上界为a的边.
- 网络流题目总结
- 网络流【复习+总结】
- 网络流算法总结
- 网络流初步总结
- 网络流 构造总结
- poj 网络流 总结
- 网络流学习总结
- 网络流总结
- 【网络流】总结
- 网络流初步总结
- 【图论】网络流总结
- 网络流题目总结
- 网络流例题总结
- 网络流总结
- 网络流建模总结
- 网络流总结
- 网络流总结篇
- 网络流总结
- HttpClient使用详解
- Visual Studio 2015正式版/产品密钥
- 八皇后问题(简单回溯)
- 星型模式
- 深入理解abstract class和interface
- 网络流总结
- OpenStack配置解析库——oslo.config
- Unity工具栏使用(二)
- socket通信之最简单的socket通信
- socket通信之最简单的I/O 多路复用
- 多进程 vfork
- socket通信之完整的多路复用
- poll函数实现多路复用
- Linux平台下C++编程