网络流详解C++版.docx
《网络流详解C++版.docx》由会员分享,可在线阅读,更多相关《网络流详解C++版.docx(34页珍藏版)》请在冰豆网上搜索。
![网络流详解C++版.docx](https://file1.bdocx.com/fileroot1/2022-12/30/65dceb4f-43d9-4088-bc49-3eb34c7e6dd8/65dceb4f-43d9-4088-bc49-3eb34c7e6dd81.gif)
网络流详解C++版
网络流基本概念
在实际生活中有许多流量问题,例如在交通运输网络中的人流、车流、货物流,供水网络中的水流,金融系统中的现金流,通讯系统中的信息流,等等。
50年代以福特(Ford)、富克逊(Fulkerson)为代表建立的“网络流理论”,是网络应用的重要组成部分。
在最近的奥林匹克信息学竞赛中,利用网络流算法高效地解决问题已不是什么稀罕的事了。
本节着重介绍最大流(包括最小费用)算法,并通过实际例子,讨论如何在问题的原型上建立—个网络流模型,然后用最大流算法高效地解决问题。
1.问题描述如图5-1所示是联结某产品地v1和销售地v4的交通网,每一弧(vi,vj)代表从vi到vj的运输线,产品经这条弧由vi输送到vj,弧旁的数表示这条运输线的最大通过能力。
产品经过交通网从v1到v4。
现在要求制定一个运输方案使从v1到v4的产品数量最多。
图5-1 图5-2
2.网络与网络流
给一个有向图N=(V,E),在V中指定一点,称为源点(记为vs,和另一点,称为汇点(记为vt),其余的点叫中间点,对于E中每条弧(vi,vj)都对应一个正整数c(vi,vj)≥O(或简写成cij),称为f的容量,则赋权有向图N=(V,E,c,vs,vt)称为一个网络。
如图5-1所给出的一个赋权有向图N就是一个网络,指定v1是源点,v4为汇点,弧旁的数字为cij。
所谓网络上的流,是指定义在弧集合E上一个函数f={f(vi,vj)},并称f(vi,vj)为弧(vi,vj)上的流量(下面简记为fij)。
如图5-2所示的网络N,弧上两个数,第一个数表示容量cij,第二个数表示流量fij。
3.可行流与最大流
在运输网络的实际问题中,我们可以看出,对于流有两个显然的要求:
一是每个弧上的流量不能超过该弧的最大通过能力(即弧的容量);二是中间点的流量为0,源点的净流出量和汇点的净流入量必相等且为这个方案的总输送量。
因此有:
(1)容量约束:
0≤fij≤cij,(vi,vj)∈E,
(2)守恒条件
对于中间点:
流入量=流出量;对于源点与汇点:
源点的净流出量vs(f)=汇点的净流入量(-vt(f))
的流f,称为网络N上的可行流,并将源点s的净流量称为流f的流值v(f)。
网络N中流值最大的流f*称为N的最大流。
4.可增广路径
所谓可增广路径,是指这条路径上的流可以修改,通过修改,使得整个网络的流值增大。
设f是一个可行流,P是从源点s到汇点t的一条路,若p满足下列条件:
(1)在p上的所有前向弧(vi→vj)都是非饱和弧,即0≤fij
(2)在p上的所有后向弧(vi←vj)都是非零弧,即0则称p为(关于可行流f的)一条可增广路径。
5.最大流定理
当且仅当不存在关于f*的增广路径,可行流f*为最大流。
5.2最大流算法
算法思想:
最大流问题实际上是求一可行流{fij},使得v(f)达到最大。
若给了一个可行流f,只要判断N中有无关于f的增广路径,如果有增广路径,改进f,得到一个流量增大的新的可行流;如果没有增广路径,则得到最大流。
1.寻求最大流的标号法(Ford,Fulkerson)
从一个可行流(一般取零流)开始,不断进行以下的标号过程与调整过程,直到找不到关于f的可增广路径为止。
(1)标号过程
在这个过程中,网络中的点分为已标号点和未标号点,已标号点又分为已检查和未检查两种。
每个标号点的标号信息表示两个部分:
第一标号表明它的标号从哪一点得到的,以便从vt开始反向追踪找出也增广路径;第二标号是为了表示该顶点是否已检查过。
标号开始时,给vs标上(s,0),这时vs是标号但末检查的点,其余都是未标号的点,记为(0,0)。
取一个标号而未检查的点vi,对于一切未标号的点vj:
A.对于弧(vi,vj),若fij B.对于弧(vi,vj),若fji>0,则给vj标号(-vi,0),这时,vj点成为标号而未检查的点。
于是vi成为标号且已检查的点,将它的第二个标号记为1。
重复上述步骤,一旦vt被标上号,表明得到一条从vi到vt的增广路径p,转入调整过程。
若所有标号都已检查过去,而标号过程进行不下去时,则算法结束,这时的可行流就是最大流。
(2)调整过程
从vt点开始,通过每个点的第一个标号,反向追踪,可找出增广路径P。
例如设vt的第一标号为vk(或-vk),则弧(vk,vt)(或相应地(vt,vk))是p上弧。
接下来检查vk的第一标号,若为vi(或-vi),则找到(vi,vk)(或相应地(vk,vi))。
再检查vi的第一标号,依此类推,直到vs为止。
这时整个增广路径就找到了。
在上述找增广路径的同时计算Q:
Q=min{min(cij-fij),minf*ij}
对流f进行如下的修改:
f'ij= fij+Q (vi,vj)∈P的前向弧的集合
f'ij= fij-Q (vi,vj)∈P的后向弧的集合
f'ij= f*ij (vi,vj)不属于P的集合
接着,清除所有标号,对新的可行流f’,重新进入标号过程。
Codevs1993草地排水
题目描述Description
在农夫约翰的农场上,每逢下雨,Bessie最喜欢的三叶草地就积聚了一潭水。
这意味着草地被水淹没了,并且小草要继续生长还要花相当长一段时间。
因此,农夫约翰修建了一套排水系统来使贝茜的草地免除被大水淹没的烦恼(不用担心,雨水会流向附近的一条小溪)。
作为一名一流的技师,农夫约翰已经在每条排水沟的一端安上了控制器,这样他可以控制流入排水沟的水流量。
农夫约翰知道每一条排水沟每分钟可以流过的水量,和排水系统的准确布局(起点为水潭而终点为小溪的一张网)。
需要注意的是,有些时候从一处到另一处不只有一条排水沟。
根据这些信息,计算从水潭排水到小溪的最大流量。
对于给出的每条排水沟,雨水只能沿着一个方向流动,注意可能会出现雨水环形流动的情形。
输入描述InputDescription
第1行:
两个用空格分开的整数N(0<=N<=200)和M(2<=M<=200)。
N是农夫John已经挖好的排水沟的数量,M是排水沟交叉点的数量。
交点1是水潭,交点M是小溪。
第二行到第N+1行:
每行有三个整数,Si,Ei,和Ci。
Si和Ei(1<=Si,Ei<=M)指明排水沟两端的交点,雨水从Si流向Ei。
Ci(0<=Ci<=10,000,000)是这条排水沟的最大容量。
输出描述OutputDescription
输出一个整数,即排水的最大流量。
样例输入SampleInput
54
1240
1420
2420
2330
3410
样例输出SampleOutput
50
Dinic算法:
#include
#include
#include
#include
#include
usingnamespacestd;
constintmn=22222;
constintmm=1000000+5;
constintinf=0x3fffffff;//1073741823
intn,m,st,sd,cnt;
intdis[mn],que[mn],work[mn];
intans;
structdata
{
intto;
intflow;
intnext;
inthead;
}e[mm];
voidinit()
{
st=1;sd=n;
for(inti=1;i<=n;i++)
{
e[i].head=0;
}
cnt=0;
}
voidaddedge(intu,intv,intc)
{
cnt++;
e[cnt].to=v;
e[cnt].flow=c;
e[cnt].next=e[u].head;
e[u].head=cnt;
}
boolbfs()
{
intu,v,l=1,h=0;
for(inti=1;i<=n;i++)dis[i]=-1;
que[1]=st;
dis[st]=0;
while(l{
h++;
u=que[h];
for(inti=e[u].head;i>0;i=e[i].next)
{
v=e[i].to;
if(e[i].flow&&dis[v]<0)
{
dis[v]=dis[u]+1;
l++;
que[l]=v;
if(v==sd)returntrue;
}
}
}
returnfalse;
}
intdfs(intu,intexp)
{
if(u==sd)returnexp;
for(inti=work[u];i>0;i=e[i].next)
{
intv=e[i].to,tp;
if(e[i].flow&&dis[v]==dis[u]+1&&(tp=dfs(v,min(e[i].flow,exp)))>0)
{
e[i].flow-=tp;
e[i+1].flow+=tp;
returntp;
}
}
return0;
}
voidDinic()
{
while(bfs())
{
for(inti=0;iwhile(dfs(st,inf));
}
}
intmain()
{
scanf("%d%d",&m,&n);
init();
for(inti=1;i<=m;i++)
{
intu,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,0);
}
Dinic();
ans=0;
for(inti=e[sd].head;i>0;i=e[i].next)
{
intv=e[i].to;
ans+=e[i].flow;
}
printf("%d\n",ans);
return0;
}
SAP算法:
#include
#include
#include
#include
usingnamespacestd;
#defineMAXN444//邻接表要开边数的2倍
structEdge{
intv,cap,next;
}edge[MAXN];
intlevel[MAXN];//标记层次(距离标号)
//间隙优化,定义gap[i]为标号是i的点的个数
//在重标记i时,检查gap[level[i]],若减为0,这算法结束。
intgap[MAXN];
intpre[MAXN];//前驱
intcur[MAXN];
inthead[MAXN];
intNV,NE;
//NE为边数,初始化为0;
voidInsert(intu,intv,intcap,intcc=0){
edge[NE].cap=cap;edge[NE].v=v;
edge[NE].next=head[u];head[u]=NE++;
edge[NE].cap=cc;edge[NE].v=u;
edge[NE].next=head[v];head[v]=NE++;
}
//参数,源点,汇点
intSAP(intvs,intvt){
memset(level,0,sizeof(level));
memset(pre,-1,sizeof(pre));
memset(gap,0,sizeof(gap));
//cur[i]保存的是当前弧
for(inti=0;i<=NV;i++)cur[i]=head[i];
intu=pre[vs]=vs;//源点的pre还是其本身
intmaxflow=0,aug=-1;
gap[0]=NV;
while(level[vs]loop:
for(int&i=cur[u];i!
=-1;i=edge[i].next){
intv=edge[i].v;//v是u的后继
//寻找可行弧
if(edge[i].cap&&level[u]==level[v]+1){
//aug表示增广路的可改进量
aug==-1?
(aug=edge[i].cap):
(aug=min(aug,edge[i].cap));
pre[v]=u;
u=v;
//如果找到一条增广路
if(v==vt){
maxflow+=aug;//更新最大流;
//路径回溯更新残留网络
for(u=pre[v];v!
=vs;v=u,u=pre[u]){
//前向弧容量减少,反向弧容量增加
edge[cur[u]].cap-=aug;
edge[cur[u]^1].cap+=aug;
}
aug=-1;
}
gotoloop;
}
}
intminlevel=NV;
//寻找与当前点相连接的点中最小的距离标号(重标号)
for(inti=head[u];i!
=-1;i=edge[i].next){
intv=edge[i].v;
if(edge[i].cap&&minlevel>level[v]){
cur[u]=i;//保存弧
minlevel=level[v];
}
}
if((--gap[level[u]])==0)break;//更新gap数组后如果出现断层,则直接退出。
level[u]=minlevel+1;//重标号
gap[level[u]]++;//距离标号为level[u]的点的个数+1;
u=pre[u];//转当前点的前驱节点继续寻找可行弧
}
returnmaxflow;
}
intmain(){
intm;//边的条数
while(~scanf("%d%d",&m,&NV)){
memset(head,-1,sizeof(head));
NE=0;
for(inti=1;i<=m;i++){
intu,v,cap;
scanf("%d%d%d",&u,&v,&cap);
Insert(u,v,cap);
}
printf("%d\n",SAP(1,NV));
}
return0;
}
Codevs1914运输问题
题目描述Description
W公司有m个仓库和n个零售商店。
第i个仓库有ai个单位的货物;第j个零售商店
需要bj个单位的货物。
货物供需平衡,即sum(si)=sum(bj)
。
从第i个仓库运送每单位货物到
第j个零售商店的费用为cij。
试设计一个将仓库中所有货物运送到零售商店的运输方案,
使总运输费用最少。
编程任务:
对于给定的m个仓库和n个零售商店间运送货物的费用,计算最优运输方案和最差运输方案。
输入描述InputDescription
的第1行有2个正整数m和n,分别表示仓库数和
零售商店数。
接下来的一行中有m个正整数ai,1≤i≤m,表示第i个仓库有ai个单位的货
物。
再接下来的一行中有n个正整数bj,1≤j≤n,表示第j个零售商店需要bj个单位的货
物。
接下来的m行,每行有n个整数,表示从第i个仓库运送每单位货物到第j个零售商店
的费用cij。
输出描述OutputDescription
将计算出的最少运输费用和最多运输费用输出
样例输入SampleInput
23
220280
170120210
7739105
150186122
样例输出SampleOutput
48500
69140
把所有仓库看做二分图中顶点Xi,所有零售商店看做二分图中顶点Yi,建立附加源S汇T。
1、从S向每个Xi连一条容量为仓库中货物数量ai,费用为0的有向边。
2、从每个Yi向T连一条容量为商店所需货物数量bi,费用为0的有向边。
3、从每个Xi向每个Yj连接一条容量为无穷大,费用为cij的有向边。
这道题其实就是求一个网络中的最小费用最大流和最大费用最大流,最小费用最大流略过,最大费用最大流有2中方法:
1、把所有费用变成相反数做一遍最小费用最大流,输出答案的相反数;
2、初始化spfa时dis数组全从max改为-1,松弛的条件从dis[i]>dis[j]+cap[i,j]改为dis[i]此处我采用了第一种方法。
#include
#include
#include
#defineinf0x7fffffff
#defineT2001
usingnamespacestd;
intm,n,head[2005],q[2005],dis[2005],from[2005],a[2005],b[2005],cnt,ans;
intc[2005][2005];
boolinq[2005];
structdata{intfrom,to,next,v,c;}e[1000001];
voidins(intu,intv,intw,intc)
{
cnt++;
e[cnt].from=u;e[cnt].to=v;
e[cnt].v=w;e[cnt].c=c;
e[cnt].next=head[u];head[u]=cnt;
}
voidinsert(intu,intv,intw,intc)
{ins(u,v,w,c);ins(v,u,0,-c);}
voidbuild(intk)
{
cnt=1;memset(head,0,sizeof(head));
for(inti=1;i<=m;i++)insert(0,i,a[i],0);
for(inti=1;i<=n;i++)insert(m+i,T,b[i],0);
for(inti=1;i<=m;i++)
for(intj=1;j<=n;j++)
insert(i,m+j,inf,k*c[i][j]);
}
boolspfa()
{
for(inti=0;i<=T;i++)dis[i]=inf;
intt=0,w=1,i,now;
dis[0]=q[0]=0;inq[0]=1;
while(t!
=w)
{
now=q[t];t++;if(t==200)t=0;
for(inti=head[now];i;i=e[i].next)
{
if(e[i].v&&dis[e[i].to]>dis[now]+e[i].c)
{
from[e[i].to]=i;
dis[e[i].to]=dis[now]+e[i].c;
if(!
inq[e[i].to])
{
inq[e[i].to]=1;
q[w++]=e[i].to;
if(w==200)w=0;
}
}
}
inq[now]=0;
}
if(dis[T]==inf)return0;return1;
}
voidmcf()
{
inti,x=inf;
i=from[T];
while(i)
{
x=min(e[i].v,x);
i=from[e[i].from];
}
i=from[T];
while(i)
{
e[i].v-=x;
e[i^1].v+=x;
ans+=x*e[i].c;
i=from[e[i].from];
}
}
intmain()
{
scanf("%d%d",&m,&n);
for(inti=1;i<=m;i++)scanf("%d",&a[i]);
for(inti=1;i<=n;i++)scanf("%d",&b[i]);
for(inti=1;i<=m;i++)
for(intj=1;j<=n;j++)
scanf("%d",&c[i][j]);
build
(1);
while(spfa())mcf();
printf("%d\n",ans);ans=0;
build(-1);
while(spfa())mcf();
printf("%d\n",-ans);
return0;
}
NOI2006最大获利
题目描述Description新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。
THU集团旗下的CS&T通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。
在前期市场调查和站址勘测之后,公司得到了一共N个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:
建立第i个通讯中转站需要的成本为Pi(1≤i≤N)。
另外公司调查得出了所有期望中的用户群,一共M个。
关于第i个用户群的信息概括为Ai,Bi和Ci:
这些用户会使用中转站Ai和中转站Bi进行通讯,公司可以获益Ci。
(1≤i≤M,1≤Ai,Bi≤N)THU集团的CS&T公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。
那么如何选择最终建立的中转站才能让公司的净获利最大呢?
(净获利=获益之和–投入成本之和)
输入描述InputDescription输入文件中第一行有两个正整数N和M。
第二行中有N个整数描述每一个通讯中转站的建立成本,依次为P1,P2,…,PN。
以下M行,第(i+2)行的三个数Ai,Bi和Ci描述第i个用户群的信息。
所有变量的含义可以参见题目描述。
输出描述OutputDescription你的程序只要向输出文件输出一个整数,表示公司可以得到的最大净获利。
样例输入SampleInput
55
12345
123
234
133
142
453
样例输出SampleOutput4
数据范围及提示DataSize&Hint选择建立1、2、3号中转站,则需要投入成本6,获利为10,因此得到最大收益4。
80