来看一道最大流模板水题,借这道题来学习一下最大流的几个算法。

分别用Edmond-Karp,Dinic ,SAP来实现最大流算法。

从运行结过来看明显SAP+当前弧优化+gap优化速度最快。

 

hiho一下 第115周:网络流一•Ford-Fulkerson算法

原题网址:http://hihocoder.com/contest/hiho115/problem/1

网络流一·Ford-Fulkerson算法

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

小Hi和小Ho住在P市,P市是一个很大很大的城市,所以也面临着一个大城市都会遇到的问题:交通拥挤。

小Ho:每到周末回家感觉堵车都是一种煎熬啊。

小Hi:平时交通也还好,只是一到上下班的高峰期就会比较拥挤。

小Ho:要是能够限制一下车的数量就好了,不知道有没有办法可以知道交通系统的最大承受车流量,这样就可以限制到一个可以一直很顺畅的数量了。

小Hi:理论上是有算法的啦。早在1955年,T.E.哈里斯就提出在一个给定的网络上寻求两点间最大运输量的问题。并且由此产生了一个新的图论模型:网络流

小Ho:那具体是啥?

小Hi:用数学的语言描述就是给定一个有向图G=(V,E),其中每一条边(u,v)均有一个非负数的容量值,记为c(u,v)≥0。同时在图中有两个特殊的顶点,源点S和汇点T。

举个例子:

其中节点1为源点S,节点6为汇点T。

我们要求从源点S到汇点T的最大可行流量,这个问题也被称为最大流问题。

在这个例子中最大流量为5,分别为:1→2→4→6,流量为1;1→3→4→6,流量为2;1→3→5→6,流量为2。

小Ho:看上去好像挺有意思的,你让我先想想。

提示:Ford-Fulkerson算法

输入

第1行:2个正整数N,M。2≤N≤500,1≤M≤20,000。

第2..M+1行:每行3个整数u,v,c(u,v),表示一条边(u,v)及其容量c(u,v)。1≤u,v≤N,0≤c(u,v)≤100。

给定的图中默认源点为1,汇点为N。可能有重复的边。

输出

第1行:1个整数,表示给定图G的最大流。

样例输入

6 7

1 2 3

1 3 5

2 4 1

3 4 2

3 5 3

4 6 4

5 6 2

样例输出

5

一、Ford-Fulkerson算法

算法讲解与图片均摘自:http://hihocoder.com/contest/hiho115/problem/1

f(u,v)实际流量c(u,v)为每条路径的容量

整个图G的流网络满足3个性质:

1. 容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。

2. 反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。

3. 流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。

对于上面例子中的图,其对应的实际流量f网络图为:

其中绿边表示例子中每条边实际使用的流量f(u,v),虚线表示实际不存在的边(v,u)。

在此基础上,假设我们用cf(u,v)来表示c(u,v)-f(u,v),则可以表示每一条边还剩下多少的流量可以使用,我们称为残留容量

假设一条边(u,v),其容量为3,即c(u,v)=3,由于边(u,v)单向,(v,u)容量为0,c(v,u)=0。

使用了流量f(u,v)=2(同时有f(v,u)=-2)

则可以表示为:cf(u,v)= c(u,v)-f(u,v)=1,  cf(v,u)= c(v,u)- f(v,u)=2。

由cf(u,v)构成的图我们称为残留网络

比如例子中的残留网络图为:

残留网络表示还可以使用的流量。

如果能从残留网络中找出一条从S到T的路径p,使得路径p上所有边的cf(u,v)都大于0,假设路径p上最小的cf(u,v)等于k,就可以使得S到T增加k的流量。

通过该条路径p使得图G的最大流得到了增加,这样的路径p被称为增广路径

Ford-Fulkerson算法的流程:

1. 将最初的图G转化为残留网络

2. 在残留网络上寻找增广路径

l  若存在增广路径,最大流量增加,同时对增广路径上的边cf(u,v)进行修改(总流量增加,路径上各边容量相应减少,反向边容量相应增加),再重复寻找增广路径。

l  若不存在增广路径,则这个图不能再增加流量了,得到最大流。

Ford-Fulkerson算法确定了解决最大流问题的基本思路,接下来的关键就是算法的实现,如何寻找增广路并实现路径的修改。

二、Edmond-Karp算法

Edmond-Karp算法的思路其实就是Ford-Fulkerson算法。

Edmond-Karp流程:

1. 将最初的图G转化为残留网络

2. 使用BFS反复寻找源点到汇点之间的增广路径。

若存在增广路径,对路径上的流量进行相应修改(总流量增加,路径上各边容量相应减少,反向边容量相应增加)。

3. 找不到增广路时,当前的流量就是最大流。

#include <algorithm>
#include <cstring>
#include <string.h>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include <cstdio>
#include <cmath>
#define LL long long
#define N 40005
using namespace std;
const int maxn=;
const int inf=0x7fffffff; struct Edge{
int u,v,c;
int next;
}edge[N];
int cnt;//边数
int head[N]; void addedge(int u,int v,int c)
{
edge[cnt].u=u; edge[cnt].v=v; edge[cnt].c=c; //正向边初始化为容量
edge[cnt].next=head[u]; head[u]=cnt++; edge[cnt].u=v; edge[cnt].v=u; edge[cnt].c=; //反向边容量初始化为0
edge[cnt].next=head[v]; head[v]=cnt++;
} bool visit[maxn]; // 记录结点i是否已访问
int pre[maxn]; //记录路径
int m,n;
int source,sink; //源点,汇点 bool bfs() //寻找从源点到汇点的增广路,若找到返回true
{
queue<int>q;
memset(pre,-,sizeof(pre));
memset(visit,false,sizeof(visit));
pre[source]=-;
visit[source]=true;
q.push(source);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-;i=edge[i].next)
{
int v=edge[i].v;
if(edge[i].c>&&!visit[v])
{
pre[v]=i;
visit[v]=true;
if(v==sink) return true; //存在增广路
q.push(v);
}
}
}
return false;
} int Edmond_Karp()
{
int maxflow=;
int delta;
while(bfs()) //反复在源点到汇点间寻找增广路
{
delta=inf;
int i=pre[sink];
while(i!=-)
{
delta=min(delta,edge[i].c); //路径上最小的容量为流量增量
i=pre[edge[i].u];
}
i=pre[sink];
while(i!=-)
{
// 路径上各边容量相应减少,反向边容量相应增加,总流量增加
edge[i].c-=delta; //增广路上的边减去使用的容量
edge[i^].c+=delta; //同时相应的反向边增加残余容量
i=pre[edge[i].u];
}
maxflow+=delta;
}
return maxflow;
} int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
int u,v,w;
memset(head,-,sizeof(head));
for(int i=;i<m;i++)
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
source=,sink=n;
printf("%d\n",Edmond_Karp());
}
return ;
}

三、Dinic算法

Dinic算法的流程:

利用BFS对残余网络分层。每个节点的层数就是源点到这个节点经过的最少边数。

DFS 寻找增广路。DFS每向下走一步必到达层数+1的节点,(标记满足dep[v]=dep[u]+1的边(u,v)为允许弧,增广路只走允许弧)。

找到增广路并相应修改后,回溯后继续寻找增广路,回溯到源点且无法继续,DFS结束

重复以上过程直到BFS分层到达不了汇点,结束。

Dinic算法《北京大学ACM暑期课讲义-网络流》讲的挺清楚的

#include <algorithm>
#include <cstring>
#include <string.h>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include <cstdio>
#include <cmath> #define N 40005
using namespace std;
int const inf = 0x3f3f3f3f;
int const MAX = ; struct Edge{
int u,v,c;
int next;
}edge[N];
int cnt;//边数
int head[N]; void addedge(int u,int v,int c)
{
edge[cnt].u=u; edge[cnt].v=v; edge[cnt].c=c;
edge[cnt].next=head[u]; head[u]=cnt++; edge[cnt].u=v; edge[cnt].v=u; edge[cnt].c=;
edge[cnt].next=head[v]; head[v]=cnt++;
} int n, m;
int dep[MAX]; //分层
int source,sink; //源点,汇点 int bfs()//BFS对残余网络分层
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep, -, sizeof(dep));
dep[source] = ; //源点层数初始化为0
q.push(source);
while(!q.empty()){
int u = q.front();
q.pop();
for(int i=head[u];i!=-;i=edge[i].next){
int v=edge[i].v;
if(edge[i].c> && dep[v] == -)
{
dep[v] = dep[u] + ;
q.push(v);
}
}
}
return dep[sink] != -; //BFS分层是否能到达汇点
} int dfs(int u, int delta)//DFS 寻找增广路,一次DFS可以寻找多条增广路
{
if(u == sink) //找到增广路
return delta;
int flow=;
for(int i=head[u];i!=-;i=edge[i].next){
int v=edge[i].v;
if(edge[i].c> && dep[v] == dep[u] + ){ //dfs从前一层向后一层寻找增广路
int tmp = dfs(v, min(delta-flow, edge[i].c));
// 路径上各边容量相应减少,反向边容量相应增加,总流量增加
edge[i].c -= tmp;
edge[i^].c+= tmp;
flow+=tmp;
}
}
if(!flow) dep[u]=-*inf;
return flow;
} int dinic()
{
int ans = , tmp;
while(bfs()){
while(){
tmp = dfs(, inf);
if(tmp == )
break;
ans += tmp;
}
}
return ans;
} int main()
{
while(~scanf("%d %d", &n, &m)){
cnt=;
memset(head,-,sizeof(head));
int u, v, c;
while(m--){
scanf("%d %d %d", &u, &v, &c);
addedge(u,v,c);
}
source=,sink=n;
printf("%d\n", dinic());
}
return ;
}

四、SAP 算法

基础思路还是残余网络分层,寻找增广路。和Dinic思路类似。

不过SAP分层只需要反向BFS一次。

关键在于Gap优化,当前弧优化。

Gap优化:

gap[i]表示dep[x]=i节点的个数。

如果一次重标号时,出现gap[i]=0,即出现断层,则源点到汇点之间出现断路,到达不了,结束算法。

当前弧优化:

对于每个点保存“当前弧”。

当前弧初始化是邻接表的第一条弧,即head[i],查找边的过程中找到一条允许弧,允许弧设为当前弧。

搜索边的过程从当前弧开始搜,因为可以保证每个点当前弧之前的边都不是允许弧。

代码参考:http://blog.csdn.net/sprintfwater/article/details/7913181

#include <algorithm>
#include <cstring>
#include <string.h>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include <cstdio>
#include <stdio.h>
#include <cmath>
#define LL long long
#define N 40005 using namespace std;
const int maxn=;
const int inf=0x7fffffff; struct Edge{
int u,v,c;
int next;
}edge[N];
int cnt;
int head[N]; void addedge(int u,int v,int c)
{
edge[cnt].u=u; edge[cnt].v=v; edge[cnt].c=c;
edge[cnt].next=head[u]; head[u]=cnt++; edge[cnt].u=v; edge[cnt].v=u; edge[cnt].c=;
edge[cnt].next=head[v]; head[v]=cnt++;
} int m,n;
int source,sink; //源点,汇点
int gap[maxn]; //gap优化
int dep[maxn]; //层数
int cur[maxn]; //当前弧优化
int path[maxn]; //用一个栈储存增广路路径 void rev_bfs() //对残余网络逆向分层
{
memset(dep,-,sizeof(dep));
memset(gap,,sizeof(gap));
queue<int>q;
dep[sink]=; //汇点sink的深度为0
gap[]=; // 层数为0的点有1个
q.push(sink);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-;i=edge[i].next)
{
int v=edge[i].v;
if(edge[i^].c>&&dep[v]==-)
{
q.push(v);
dep[v]=dep[u]+;
gap[dep[v]]++;
}
}
}
} int SAP()
{
rev_bfs(); //只需要bfs分层一次,之后的层数更新不用重新bfs
// for(int i=1;i<=n;i++) cout<<dep[i]<<endl;
memcpy(cur, head,sizeof(cur)); //当前弧初始化是邻接表的第一条弧,即head[i]
int maxflow = ;
int u=source;
int top=;
int i;
while (dep[source] < n) //最大的层数只会是n,如果大于等于n说明中间已经断层了
{
if (u==sink) //找到了一条增广路,则沿着增广路修改流量
{
int delta=inf;
int flag=n; //flag记录增广路上容量最小的边
for (i=; i!=top; i++){
if (delta>edge[path[i]].c)
{
delta=edge[path[i]].c;
flag=i;
}
}
for (i=;i!=top;i++) // 路径上各边容量相应减少,反向边容量相应增加,总流量增加
{
edge[path[i]].c-=delta;
edge[path[i]^].c+=delta;
}
maxflow += delta;
top = flag; //回溯到流量恰好变为0的最上层节点,继续寻找增广路
u = edge[path[top]].u;
}
for (i = cur[u]; i != -; i = edge[i].next)
{
int v=edge[i].v;
if (edge[i].c> && dep[u]==dep[v]+) break;
}
if (i!=-) //找到一条允许弧
{
cur[u]=i; //允许弧设为当前弧
path[top++]=i;
u=edge[i].v;
}
else //找不到允许弧,重新分层,再寻找增广路
{
//对u节点层数进行修改
if (--gap[dep[u]] == ) break;// gap优化,如果出现断层,结束算法
int mind = n+;
for (i = head[u]; i != -; i = edge[i].next) //寻找可以增广的最小层数
{
if (edge[i].c> && mind>dep[edge[i].v])
{
mind=dep[edge[i].v];
cur[u]=i; //允许弧设为当前弧
}
}
dep[u]=mind+; //更新层数
gap[dep[u]]++;
u=(u==source)? u : edge[path[--top]].u; //回溯
}
}
return maxflow;
} int main()
{
while(~scanf("%d%d",&n,&m))
{
int u,v,w;
cnt=;
memset(head,-,sizeof(head));
for(int i=;i<m;i++)
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
source=,sink=n;
printf("%d\n",SAP());
}
return ;
}

hiho一下 第115周:网络流一•Ford-Fulkerson算法 (Edmond-Karp,Dinic,SAP)的更多相关文章

  1. ACM/ICPC 之 网络流入门-Ford Fulkerson与SAP算法(POJ1149-POJ1273)

    第一题:按顾客访问猪圈的顺序依次构图(顾客为结点),汇点->第一个顾客->第二个顾客->...->汇点 //第一道网络流 //Ford-Fulkerson //Time:47M ...

  2. hiho一下115周 网络流

    小Hi和小Ho住在P市,P市是一个很大很大的城市,所以也面临着一个大城市都会遇到的问题:交通拥挤. 小Ho:每到周末回家感觉堵车都是一种煎熬啊. 小Hi:平时交通也还好,只是一到上下班的高峰期就会比较 ...

  3. 圆内,求离圆心最远的整数点 hiho一下第111周 Farthest Point

    // 圆内,求离圆心最远的整数点 hiho一下第111周 Farthest Point // 思路:直接暴力绝对T // 先确定x范围,每个x范围内,离圆心最远的点一定是y轴两端的点.枚举x的范围,再 ...

  4. 网络流-最大流问题 ISAP 算法解释(转自Renfei Song's Blog)

    网络流-最大流问题 ISAP 算法解释 August 7, 2013 / 编程指南 ISAP 是图论求最大流的算法之一,它很好的平衡了运行时间和程序复杂度之间的关系,因此非常常用. 约定 我们使用邻接 ...

  5. HDU3549 Flow Problem(网络流增广路算法)

    题目链接. 分析: 网络流增广路算法模板题.http://www.cnblogs.com/tanhehe/p/3234248.html AC代码: #include <iostream> ...

  6. 网络流入门--最大流算法Dicnic 算法

    感谢WHD的大力支持 最早知道网络流的内容便是最大流问题,最大流问题很好理解: 解释一定要通俗! 如右图所示,有一个管道系统,节点{1,2,3,4},有向管道{A,B,C,D,E},即有向图一张.  ...

  7. hiho一下116周 网络流

    网络流二·最大流最小割定理 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi:在上一周的Hiho一下中我们初步讲解了网络流的概念以及常规解法,小Ho你还记得内容么? ...

  8. hiho一下,第115周,FF,EK,DINIC

    题目1 : 网络流一·Ford-Fulkerson算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho住在P市,P市是一个很大很大的城市,所以也面临着一个 ...

  9. hiho 第118周 网络流四·最小路径覆盖

    描述 国庆期间正是旅游和游玩的高峰期. 小Hi和小Ho的学习小组为了研究课题,决定趁此机会派出若干个调查团去沿途查看一下H市内各个景点的游客情况. H市一共有N个旅游景点(编号1..N),由M条单向游 ...

随机推荐

  1. 【POI】导出excel文件,不生成中间文件,直接将内存中的数据创建对象下载到浏览器

    不是从InputStream中read,然后outputStream再write @RequestMapping("download4Excel") public void dow ...

  2. C#实现在Form上截取消息的两种方法

    比较常用的是重载Form的DefWndProc方法,例如截取鼠标按下的消息: protected override void DefWndProc(ref Message m) { if ( m.Ms ...

  3. 本地启动tomcat的时候报内存溢出错误:java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: PermGen space

    问题分析: PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Load ...

  4. jstat命令的使用及VM Thread分析

    http://www.javatang.com/archives/2017/10/20/12131956.html 前面提到了一个使用jstack的shell脚本,通过命令可以很快地定位到指定线程对应 ...

  5. SparkSQL的3种Join实现

    引言 Join是SQL语句中的常用操作,良好的表结构能够将数据分散在不同的表中,使其符合某种范式,减少表冗余.更新容错等.而建立表和表之间关系的最佳方式就是Join操作. 对于Spark来说有3中Jo ...

  6. Android之TextView的Span样式源代码剖析

    Android中的TextView是个显示文字的的UI类.在现实中的需求中,文字有各式各样的样式,TextView本身没有属性去设置实现.我们能够通过Android提供的 SpannableStrin ...

  7. postman里面的mockserver使用方法

    转载:http://blog.csdn.net/Cloud_Huan/article/details/78326159 首先说下mockserver是干啥的,从英文翻译理解就是模拟一个服务器,通俗点说 ...

  8. call、aply、bind的常用方法总结

    类函数变为数组 function aaa (){ Array.prototype.slice(arguments); } 进一步操作它的每个元素 function bbb() { Array.prot ...

  9. Oracle基础(二) 创建数据库实例

    Oracle中数据库也称为数据库实例,在Oracle在安装过程中可以选择创建数据库,也可以再安装之后再进行创建. 一般创建数据库可以使用图形界面方式和SQL指令的方式,由于SQL指令方式较为复杂,这里 ...

  10. LoadRunner调用md5方法

    LoadRunner调用md5方法 上一篇 / 下一篇  2011-04-29 11:25:12 / 个人分类:Loadrunner 查看( 958 ) / 评论( 0 ) / 评分( 0 / 0 ) ...