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

分别用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. Android Handler 消息循环机制

    前言 一问起Android应用程序的入口,很多人会说是Activity中的onCreate方法,也有人说是ActivityThread中的静态main方法.因为Java虚拟机在运行的时候会自动加载指定 ...

  2. 前后台JSON传值得一个问题和异常处理net.sf.json.JSONException: Unquotted string '"name"'

    项目中做导入的时候遇到个bug,用JSON.stringify()序列号json对象传给后台:然后后台通过getPatameter()获取值时,前台的英文引号变成了中文引号. 原来代码如下:(自己排查 ...

  3. 【AS3 Coder】任务五:Flash 2D游戏的第二春(下)

    在上一节中,我们基本上已经讲完了游戏中最主要的逻辑部分,不过为了更加全面地运用Starling中的一些特性,在本节中我们将一起来看看如何实现多面板切换以及粒子效果,这两个玩意儿可是比较频繁会出现于St ...

  4. testng执行报错:org.testng.TestNGException: Cannot find class in classpath

    org.testng.TestNGException: Cannot find class in classpath 解决办法:project->clean 再次执行正常运行  

  5. mysql二进制安装及基础操作

    mysql二进制安装及基础操作 环境说明: 系统版本    CentOS 6.9 x86_64 软件版本    mysql-5.6.36-linux-glibc2.5-x86_64 1.安装 采用二进 ...

  6. [Tools] Create a Simple CLI Tool in Node.js with CAC

    Command-line tools can help you with all sorts of tasks. This lesson covers the very basics of setti ...

  7. EffectiveJava(13)使类和成员的可访问性最小化

    1.为什么要使类和成员可访问性最小化 它可以有效地解除组成系统的各模块之间的耦合关系,使得这些模块可以独立的开发 测试 优化 使用 理解和修改.提高软件的可重用性 2.成员的访问级别 私有(priva ...

  8. 系统封装 如何打造原生WINPE

    1 安装微软的AIK(Windows Automated Installation Kit,Windows自动安装工具包),AIK简体中文版下载地址: http://download.microsof ...

  9. python中MySQLdb模块用法实例

    篇文章主要介绍了python中MySQLdb模块用法,以实例形式详细讲述了MySQLdb模块针对MySQL数据库的各种常见操作方法,非常具有实用价值,需要的朋友可以参考下 本文实例讲述了python中 ...

  10. nginx静态文件缓存

    open_file_cache max=65535 inactive=30s; open_file_cache 打开缓存的同时也指定了缓存最大数目,以及缓存的时间 open_file_cache_va ...