浅谈网络流
来源:互联网 发布:2ne1 解散 知乎 编辑:程序博客网 时间:2024/05/16 11:37
网络流
网络流的基本定义就不说了,这里主要讲讲我学习网络流的一些心得.
注意:
①最大流的图一定是一个有向图!
②FF,EK算法的核心在于添加反向边!
③SAP和Dinic分别对于稀疏图和稠密图更占优势!两个算法都要掌握!
④对于每一次添加完反向边后得到的新图可称之为“残余网络”!
⑤一条弧即对应着一条边
⑥增广路用的很广,网络流,匈牙利等算法都运用到了这个思想,其实就是每次用DFS或BFS找一条路径,去更新答案.
最大流
模型
给定一个源点
算法一:Ford-Fulkerson
此算法的想法非常简单:
每次在图里从源点到汇点找一条可行路径,然后把这条路塞满.
这条路径上最小的容量限度就是这整条路径的可行流量.
然后再把路径上的每条边的限度流量,都减去可行流量.
再把新得到的图继续以上操作,直到不能再有流量通过,即完成了求解.
但如果只这么贪心,显然是有问题的.
如下图:
源点
一条路径
但显然,有一种更优的策略:
总共
然后发现,在走
所以此算法最重要的一步就是添加反向边.
我们把对应的边
于是添加反向边后,就有总共
添加反向边的目的就是让贪心活起来,倒一些水回去,使得贪心最优.
如何理解反向边
首先,为什么要去理解它?
是因为,不知道为什么添加了反向边答案只会优而不会错?为什么添了反向边留回来的水一定会被另一条道再流过去?
看上图.
原本有一条b->a的路径,假设可以流n的流量,添加反向边a->b后,假设可以再流n-k的流量过来.
于是汇点可能多了总共2n-k的流量.
现在要证明的是,这2n-k的流量是可以从源点到达汇点的.
很显然,到a点的流量,通过a->b这一条路径流向了b,再由b流向汇点.
但是从a->b的流量一定不会超过n,所以从a->b的流量可以直接从a流向汇点,同理,b->a的流量也可以直接从b流向汇点.
于是这2n-k的流量就可以到汇点了.
具体的流量分配如下:
当然,这样子的路径只有一条,所以好理解.
当边多时其实只要明白对于每一条路径添加反向边都是正确的,答案只会优而不会错,从而对于全局一条路径都填上反向边也是正确的.
FF弊端
但这种方法也有很大的弊端,因为如果有一个流量的很大的管道,却每次连接的是一个很小的管道,效率就低了.
如何避免此算法的弊端?
算法二:Edmonds-Karp
对于算法一FF,我们有两个优化:
1.每次找弧数最少的一条增广路增广,保证不多走,其实就是把FF的深搜改成bfs.
2.每次增广时找一条流量限度尽量大的路,保证不少流.
这两点就可有效的避免上述算法的弊端.
具体实现中还设了
假设当前拓展点为
因为每次
注意模板中实现的一些细节,可以使常数变小,如下面这个code是蒟蒻我无意中找到的一个模板,用了两个while,还比较好理解.
Code
var n,m,i,j,x,y,p,max,tot,total:longint; f:array[0..2000,0..2000] of longint; a,b:array[0..2000] of longint; bz:array[0..2000] of boolean;function min(x,y:longint):longint;begin if x<y then exit(x) else exit(y);end;begin readln(n,m); for i:=1 to n do begin readln(x,y,p); inc(f[x,y],p); end; while true do begin fillchar(a,sizeof(a),0); fillchar(b,sizeof(b),0); fillchar(bz,sizeof(bz),0); a[1]:=maxlongint; //很重要的一步,是一个处理技巧. while true do begin tot:=0; for i:=1 to m do if not bz[i] and (a[i]>a[tot]) then tot:=i; //每次找当前没有走过点的最大容量的点,并从这个点继续往下输送流量 bz[tot]:=true; //tot即为当前输送流量的点——拓展点 max:=a[tot]; if (tot=0) then begin writeln(total); halt; end else if tot=m then break; for i:=1 to m do begin p:=min(max,f[tot,i]); //保证不能超过拓展点的最大容量 if p>a[i] then begin a[i]:=p; //取最大流 b[i]:=tot; //记录路径 end; end; end; inc(total,a[m]); //ai即代表从源点到i当前bfs路径的最大容量 i:=m; while i>1 do begin dec(f[b[i],i],a[m]); inc(f[i,b[i]],a[m]); i:=b[i]; end; //添加反向边 end; writeln(total);end.
算法三:SAP
一个定义
我们设
则
这也是SAP的核心!
允许弧
当满足
因为会有残余网络,所以
一开始的
距离标号
当找不到允许弧时,
我们就可以把
正确性显然的.
储存流量
此外我们还需要一个
我们走
可是走完后,我们发现,还有
经过标记后正确性显然.
从中也可以很明显地看出“储存流量”的作用.
从头再来
这点也是与dinic最不相同的地方,导致了为什么适用于不同的图.
SAP-1
var flag:boolean; f:array[0..1000,0..1000] of longint; dis,pre,bz:array[0..10000] of longint; x,y,z,n,m,i,j,k,max,min,ans:longint;begin assign(input,'SAP1.in'); reset(input); assign(output,'SAP1.out'); rewrite(output); readln(n,m); if n=0 then //特殊判断 begin writeln(0); halt; end; for i:=1 to n do begin readln(x,y,z); inc(f[x,y],z); //构图 end; i:=1; max:=maxlongint; while dis[1]<m do //dis[1]即1的距离标号 begin bz[i]:=max; //储存流量 flag:=false; for j:=1 to m do if (f[i,j]>0) and (dis[j]+1=dis[i]) then begin if f[i,j]<max then max:=f[i,j]; //更新流量 flag:=true; pre[j]:=i; //记录前驱 i:=j; if i=m then //找到一条增广路,并从源点开始重新拓展 begin inc(ans,max); while i<>1 do begin dec(f[pre[i],i],max); inc(f[i,pre[i]],max); i:=pre[i]; end; max:=maxlongint; end; break; end; if flag then continue; //找到了增广路就无需更新距离标号 min:=m-1; for j:=1 to m do if (f[i,j]>0) and (dis[j]<min) then begin k:=j; min:=dis[j]; end; //更新距离标号 dis[i]:=min+1; if i<>1 then begin i:=pre[i]; max:=bz[i]; //退一步重新再找 end; end; writeln(ans); close(input); close(output);end.
但实际上,
当前弧优化
我们先用一个
显然初始值
那么每次从一个节点
如果是在更新距离标号时,也可以同样更新
SAP-2
var flag:boolean; f:array[0..1000,0..1000] of longint; dis,pre,bz,h:array[0..10000] of longint; x,y,z,n,m,i,j,k,max,min,ans:longint;begin assign(input,'SAP2.in'); reset(input); assign(output,'SAP2.out'); rewrite(output); readln(n,m); if n=0 then begin writeln(0); halt; end; for i:=1 to n do begin readln(x,y,z); inc(f[x,y],z); end; for i:=1 to m do h[i]:=1; //初值 i:=1; max:=maxlongint; while dis[1]<m do begin bz[i]:=max; flag:=false; for j:=h[i] to m do //当前弧优化 if (f[i,j]>0) and (dis[j]+1=dis[i]) then begin if f[i,j]<max then max:=f[i,j]; flag:=true; pre[j]:=i; h[i]:=j; //注意,不可以是j+1 i:=j; if i=m then begin inc(ans,max); while i<>1 do begin dec(f[pre[i],i],max); inc(f[i,pre[i]],max); i:=pre[i]; end; max:=maxlongint; end; break; end; if flag then continue; min:=m-1; for j:=1 to m do if (f[i,j]>0) and (dis[j]<min) then begin k:=j; min:=dis[j]; end; h[i]:=k; //注意,不可以是k+1 dis[i]:=min+1; if i<>1 then begin i:=pre[i]; max:=bz[i]; end; end; writeln(ans); close(input); close(output);end.
GAP优化
假设一次重新标号后,出现距离断层,也就是距离标号为
虽然这个优化非常简单,但实际意义很大.
SAP-3
var flag:boolean; f:array[0..1000,0..1000] of longint; h,d,bz,dis,pre:array[0..100000] of longint; x,y,z,n,m,i,j,k,max,ans,min:longint;begin readln(n,m); for i:=1 to n do begin readln(x,y,z); inc(f[x,y],z); end; d[0]:=m; //di表示距离标号为i的点的个数 for i:=1 to m do h[i]:=1; i:=1; max:=maxlongint; while dis[1]<m do begin bz[i]:=max; flag:=false; for j:=h[i] to m do if (f[i,j]>0) and (dis[i]=dis[j]+1) then begin if f[i,j]<max then max:=f[i,j]; h[i]:=j; pre[j]:=i; i:=j; flag:=true; if i=m then begin inc(ans,max); while i>1 do begin dec(f[pre[i],i],max); inc(f[i,pre[i]],max); i:=pre[i]; end; max:=maxlongint; end; break; end; if flag then continue; min:=m-1; for j:=1 to m do if (f[i,j]>0) and (dis[j]<min) then begin min:=dis[j]; k:=j; end; h[i]:=k; dec(d[dis[i]]); //更新 if d[dis[i]]=0 then break; //GAP优化 dis[i]:=min+1; inc(d[dis[i]]); //更新 if i>1 then begin i:=pre[i]; max:=bz[i]; end; end; writeln(ans);end.
算法四:Dinic
我是先学Sap,再学dinic,两个算法其实核心思想没有什么太大的不同,关键在于细节方面的不同导致了两个算法的优缺点.
dinic之所以快,在于他每次找完一条路径后是利用Dfs的特性直接往回跳到还能继续走的地方,而sap是从头再来,所以这里dinic在边数较多的情况下比较占优.
但dinic需要每次都分层,而sap是直接在“一次bfs”中分层,基于这一点,sap更优.
好啦好啦,以上都是关于dinic和Sap的不同,下面我们来具体讲解一下有关dinic的相关知识.
在了解dinic之前,我们可以接触一下这个算法:
这个算法的步骤是:
①初始化流量,计算出剩余图
②根据剩余图计算层次图。若汇点不在层次图内,则算法结束
③在层次图内不断用bfs增广,直到层次图内没有增广路为止
④转步骤②
然后我们可以惊奇的发现,dinic其实就是把步骤③中的用bfs增广改成dfs.
但这样可以大大提高效率,具体原因在上面讨论sap和dinic不同时已经讲过.
贴一个优(chou)美(lou)的代码:
var d,tov,next,last,len,dis:array[1..400] of longint; n,m,i,j,tot,head,tail,x,y,z,ans:longint;function min(x,y:longint):longint; begin if x<y then exit(x); exit(y); end;procedure insert(x,y,z:longint);begin inc(tot); len[tot]:=z; tov[tot]:=y; next[tot]:=last[x]; last[x]:=tot;end;function bfs:boolean;begin head:=0; tail:=1; d[tail]:=1; fillchar(dis,sizeof(dis),0); dis[tail]:=1; while head<tail do begin inc(head); x:=last[d[head]]; while x>0 do begin if (dis[tov[x]]=0) and (len[x]>0) then begin inc(tail); d[tail]:=tov[x]; dis[tov[x]]:=dis[d[head]]+1; end; x:=next[x]; end; end; exit(dis[m]>0);end;function dfs(k,max:longint):longint;var x,flow:longint;begin if (k=m) or (max=0) then exit(max); dfs:=0; x:=last[k]; while x>0 do begin if (dis[tov[x]]=dis[k]+1) and (len[x]>0) then begin flow:=dfs(tov[x],min(max,len[x])); if flow>0 then begin dec(len[x],flow); inc(len[x+1],flow); //前向星导致如此更值 dec(max,flow); //注意这一步,dfs的关键 inc(dfs,flow); //返回一个ans值供答案更新 if max=0 then break;//这里只是为了常数 end; end; x:=next[x]; end;end;begin readln(n,m); for i:=1 to n do begin readln(x,y,z); insert(x,y,z); insert(y,x,0); end; while bfs do ans:=ans+dfs(1,maxlongint); writeln(ans);end.
最小割
割的定义
若在一个图里,每条边都有一些权值,删去一些边后,源点和汇点不联通,则删去的这些边叫做图的一个割集.
抽象化的说,如果把边集
最小割的定义
既然都知道了割,那么显然最小割就是每种割法中删去的边的权值和的最小值.
一个定理
最小割=最大流.
于是最小割就被完美解决了.
最大权闭合子图
闭合子图的定义
若一个图中任意点的后继也都在图中,则这个图称为闭合子图.
最大权闭合子图
顾名思义就是所有闭合子图中点权和最大的图.
分集——二分图
我们把点集
这样的分集后形成的图为二分图.
那么有以下几个简单的结论:
①最小割为简单割
②闭合图是简单割
③简单割是闭合图
④最小割所产生的两个联通分量中,源点所在的联通分量的点权和为最大权闭合子图
证明:
我们先定义
先设一个简单割的容量为
再设闭合图
即
因为
- 浅谈网络流
- 浅谈上下界网络流
- (坑)网络流浅谈
- 浅谈网络流的基本算法 [转]
- 浅谈网络流的基本算法
- POJ 3281 浅谈网络流基础建模
- BZOJ 1711 浅谈网络流大水题
- 浅谈网络信息挖掘
- 浅谈网络信息挖掘
- 浅谈网络信息挖掘
- 浅谈无线传感器网络
- 浅谈网络语音技术
- 浅谈网络语音技术
- 浅谈网络语音技术
- 浅谈网络语音技术
- 浅谈小世界网络
- 浅谈oracle网络配置文件
- 浅谈网络语音技术
- 使用原生的ajax判断用户名是否占用?
- android下Maven环境配置
- 1005. 继续(3n+1)猜想
- java 万物皆对象(Object)
- Android 你画我猜核心实现源码,客户端+客户端
- 浅谈网络流
- java集合类设计中的几个问题
- kafka 名词概念
- JS实现简单幻灯片
- Spring Cloud Config 实践
- C/C++学习之路之函数
- 菜鸟诞生————caffe之mnist图片数据集转lmdb
- 读取和修改caffemodel文件里的参数——by 蠢鱼
- caffe mnist训练