http://trp.jlu.edu.cn/software/net/lssx/4/4.38.htm

http://www.cnblogs.com/zen_chou/archive/0001/01/01/1525841.html

一、 引言

图论这门古老而又年轻的学科在信息学竞赛中占据了相当大的比重。其中,网络流算法经常在题目中出现。网络流涵盖的知识非常丰富,从基本的最小割最大流定理到网络的许多变形再到最高标号预流推进的六个优化等等,同学们在平时需要多多涉猎这方面的知识,不断积累,才能应对题目的各种变化。

随着信息学竞赛的不断发展,其题目的难度以及考察范围都不断增大。现在,对于一些新出现的题目,仅仅掌握最朴素的网络流算法并不足以解决问题。本文针对一些数据规模比较大的网络流题目详细介绍了基于分层思想的3个网络流算法,并通过列举和比较说明了其在解题中的应用,而对一些基础的知识,如最小割最大流定理等,没有作具体阐释,大家可以在许多其他网络流资料中找到。

二、预备概念

2.1剩余图的概念

给定一个流量网络、源点、汇点、容量函数,以及其上的流量函数。我们这样定义对应的剩余图:剩余图中的点集与流量网络中的点集相同,即。对于流量网络中的任一条边,若,那么边,这条边在剩余图中的权值为;同时,若那么边,这条边在剩余图中的权值为。

我们可以发现,流量网络中的每条边在剩余图中都化作一条或二条边。剩余图中的每条边都表示在原流量网络中能沿其方向增广。剩余图的权值函数表示在流量网络中能够沿着的方向增广大小为的流量。所以在剩余图中,从源点到汇点的任意一条简单路径都对应着一条增广路,路径上每条边的权值的最小值即为能够一次增广的最大流量。

2.2顶点的层次

在剩余图中,我们把从源点到点的最短路径长度称作点的层次,记为。源点的层次为0。在下面这张剩余图中:

每个点旁边的数字即表示该点在图中的层次。

2.3层次图的概念

我们这样定义层次图:对于剩余图中的一条边,当且仅当时,边;

直观地讲,层次图是建立在剩余图基础之上的一张“最短路图”。从源点开始,在层次图中沿着边不管怎么走,经过的路径一定是终点在剩余图中的最短路。

2.4阻塞流的概念

在流量网络中存在一可行流,当该网络的层次图中不存在增广路时,我们称流函数为层次图的阻塞流。

三、最短路径增值算法(MPLA)的步骤及复杂度分析

3.1算法步骤

之前我们讲到的层次图将被应用在最短路径增值算法中。首先,我们看一下最短路径增值算法的步骤:

1、初始化流量,计算出剩余图

2、根据剩余图计算层次图。若汇点不在层次图内,则算法结束

3、在层次图内不断用bfs增广,直到层次图内没有增广路为止

4、转步骤2

算法中,2、3步被循环执行,我们将执行2、3步的一次循环称为一个阶段。每个阶段中,我们首先根据剩余图建立层次图,然后不断用bfs在层次图内增广,寻找阻塞流。增广完毕后,进入下一个阶段。这样不断重复,直到汇点不在层次图内出现为止。汇点不在层次图内意味着在剩余图中不存在一条从源点到汇点的路径,即没有增广路。

在程序实现的时候,层次图并不用被“建”出来,我们只需对每个顶点标记层次,增广的时候,判断边是否满足这一约束即可。

3.2定理的证明

定理:对于有n个点的流量网络,在最短路径增值算法中,最多有n个阶段。

也就是说,在算法中层次图最多被建立n次。证明这个定理有助于我们进行算法复杂度分析。

证明:

在建立完层次图以后,假设从源点到汇点的最短路径长度为k,我们将层次图中所有的点分到k+1个集合中,第i个集合为,如下图所示:

在剩余图中,存在着2类边。

第一类:从第个集合中的顶点连到第个集合中的顶点

第二类:从第个集合中的顶点连到第个集合中的顶点

在层次图中,只存在第一类边,这是由层次图的性质决定的。我们所要找的增广路中的边也必定是第一类边。

当我们对一条增广路径增广后,会删除一条或多条增广路中的饱和边,也就是第一类边;而同时会在剩余图中加入一些与增广路径中的边反向的边。这些新加入的边一定是第二类边。如下图所示,在剩余图(a)中,找到一条从左向右的增广路径,能够增广的流量大小为2。增广后的结果是剩余图(b)。可以发现,在剩余图(a)里面,中间一条红色第一类边在增广后饱和而被删除了,同时,在剩余图(b)中,新增了2条绿色的第二类边。

当我们在层次图中找到阻塞流之后,层次图中就不存在从第一个集合一步一步往下走,最后达到第k+1个集合的长为k的路径了。而此时不在层次图中的边都是第二类边。我们可以发现,这个时候在剩余图中的最短路径一定是这样:从源点开始,往下一步一步走,走到某个集合后沿着第二类边向上退至某个集合,再继续一步一步向下走,到某个集合又向上退…………直到走到汇点。

因为必然会经过第二类边,而经过的第一类边的数量>=k,所以路径总长度一定大于k。这即是下一个阶段的最短路径长度。

由此,我们得出了一个结论:

结论:层次图中增广路径长度随阶段而严格递增。

因为增广路径长度最短是1,最长是n-1 ,再算上汇点不在层次图内的最后一次,层次图最多被建造n次,所以最短路径增值算法最多有n个阶段。

证毕。

3.3复杂度分析

3.3.1建造层次图的复杂度分析

我们将复杂度分析分为建层次图和找增广路两部分讨论。

前面已经证明了,在最短路径增值算法中,最多建n个层次图,每个层次图用bfs一次遍历即可得到。一次bfs遍历的复杂度为,所以建层次图的总复杂度为。

3.3.2增广复杂度分析

我们首先分析在每一阶段中找增广路的复杂度。

注意到每增广一次,层次图中必定有一条边会被删除。层次图中最多有m条边,所以可以认为最多增广m次。在最短路径增广中,我们用bfs来增广。一次增广的复杂度为。其中为bfs的花费,为修改流量的花费。

所以在每一阶段的复杂度为

这样,得到找增广路总的复杂度为

最短路径增值算法的总复杂度即为建层次图的总复杂度与找增广路的总复杂度之和,为。

四、Dinic算法的步骤以及复杂度分析

4.1算法步骤

Dinic算法的思想也是分阶段地在层次图中增广。它与最短路径增值算法不同之处是:在Dinic算法中,我们用一个dfs过程代替多次bfs来寻找阻塞流。下面给出其算法步骤:

1、初始化流量,计算出剩余图

2、根据剩余图计算层次图。若汇点不在层次图内,则算法结束

3、在层次图内用一次dfs过程增广

4、转步骤2

在Dinic的算法步骤中,只有第三步与最短路径增值算法不同。之后我们会发现,dfs过程将会使算法的效率较之MPLA有非常大的提高。

下面是dfs的过程:

pßs;

While outdegree(s)>0

ußp.top;

if u<>t

if outdegree(u)>0

设(u,v)为层次图中的一条边;

pßp,v;

else

从p和层次图中删除点u,

以及和u连接的所有边;

else

增广p(删除了p中的饱和边);

令p.top为p中从s可到达的最后顶点;

end while

在程序里,p表示找到的增广路径,p.top为路径中的最后一个顶点。一开始,p中只有源点。

整个While循环分为2个操作。如果p的最后一个顶点为汇点,也就是说找到了增广路,那么对p增广,注意到增广后一定有一条或多条p中的边被删除了。这时,我们使增广路径后退至p中从源点可到达的最后一个顶点。

如果p的最后一个顶点不为汇点,那么观察最后那个的顶点u 。若在层次图中存在从u连出的一条边,比如(u,v),我们就将顶点v放入路径p中,继续dfs遍历;否则,点u对之后的dfs遍历就没有用了,我们将点u以及层次图中连到u的所有边删除,并且在p中后退一个点。

Dfs过程将会不断重复这2个操作,直到从源点连出的边全部被删除为止。

下面给出一个dfs的图例,图中,红边代表找到的增广路p中的边。

4.2复杂度分析

和在最短路径增值算法中的证明一样,Dinic算法最多被分为n个阶段。

这样首先可以得到Dinic算法中建立层次图的总复杂度仍是。

我们再来分析dfs过程的总复杂度。在每一阶段,将dfs分成2部分分析:

pßs;

While outdegree(s)>0

ußp.top;

if u<>t

if outdegree(u)>0

设(u,v)为层次图中的一条边;

pßp,v;

else

从p和层次图中删除点u,

以及和u连接的所有边;

else

增广p(删除了p中的饱和边);

令p.top为p中从s可到达的最后顶点;

end while

(1) 修改增广路的流量并后退的花费:(即为代码中红框对应的部分)

    在3.3.2小节中我们讲到过,在每一阶段,最多增广m次,每次修改流量的费用为。而一次增广后在增广路中后退的费用也为。所以在每一阶段,修改增广路以及后退的复杂度为。

(2) Dfs遍历时的前进与后退:(即为代码中篮框对应的部分)

    我们将用到3.1小节中的知识。

    在dfs遍历时,如果当前路径的最后一个顶点能够继续扩展,则一定是沿着第一类边向汇点前进了一步。因为增广路径长度最长为n,所以最多连续前进n步后就会遇到汇点。在前进的过程中,可能会遇到没有边能够沿着继续前进的情况,这时,我们将路径中的最后一个点在层次图中删除并出栈。

    注意到每后退一次必定会删除一个点,所以后退的次数最多为n次。在每一阶段中,后退的复杂度为。

    假设在最坏情况下,所有的点最后均被删除,一共后退了n次,这也就意味着,有n次的前进被“无情”地退了回来,这n次前进操作都打了水漂。除去这n次前进和n次后退,其余的前进都对最后找到增广路作了贡献。增广路最多找m次,每次最多前进n个点。所以所有前进操作最多为次,复杂度为。

    于是我们得到:在每一阶段中,dfs遍历时前进与后退的花费为。

综合以上二点,一次dfs的复杂度为。因为最多进行n次dfs,所以在Dinic算法中找增广路的总复杂度为,这也是Dinic算法的总复杂度。

代码:效率较高的

#include<stdio.h>

#include<string.h>

#define inf 0x3fffffff

#define N 600

struct node

{

int u,v,flow,next;

}map[4*N];

int n,m,s,t,adj[N],dis[N],q[N],num;

int min(int a,int b)

{

return a<b?a:b;

}

void insert(int u,int v,int w)

{

map[num].u=u;

map[num].v=v;

map[num].flow=w;

map[num].next=adj[u];

adj[u]=num;

num++;

map[num].u=v;

map[num].v=u;

map[num].flow=0;

map[num].next=adj[v];

adj[v]=num;

num++;

}

int bfs()

{

int i,x,v,tail=0,head=0;

memset(dis,0,sizeof(dis));

dis[s]=1;

q[tail++]=s;

while(head<tail)

{

x=q[head++];

for(i=adj[x];i!=-1;i=map[i].next)

{

v=map[i].v;

if(map[i].flow&&!dis[v])

{

dis[v]=dis[x]+1;

if(v==t)

return 1;

q[tail++]=v;

}

}

}

return 0;

}

int dfs(int s,int limit)

{

if(s==t)

return limit;

int i,temp,cost=0,v;

for(i=adj[s];i!=-1;i=map[i].next)

{

v=map[i].v;

if(map[i].flow&&dis[s]==dis[v]-1)

{

temp=dfs(v,min(limit-cost,map[i].flow));

if(temp>0)

{

map[i].flow-=temp;

map[i^1].flow+=temp;

cost+=temp;

if(limit==cost)

break;

}

else

dis[v]=-1;

}

}

return cost;

}

int dinic()

{

int ans=0;

while(bfs())

ans+=dfs(s,inf);

return ans;

}

int main()

{

while(~scanf("%d%d",&m,&n))

{

int u,v,w;

memset(adj,-1,sizeof(adj));

num=0;

s=1;t=n;

while(m--)

{

scanf("%d%d%d",&u,&v,&w);

insert(u,v,w);

}

printf("%d\n",dinic());

}

return 0;

}

效率有点低的:

#include<stdio.h>

#include<string.h>

#include<queue>

#define N 300

#define inf 0x3fffffff

using namespace std;

int min(int a,int b)

{

return a<b?a:b;

}

int dis[N],n;//dis[i],表示到原点s的层数

int flow[N][N];

int bfs()// 重新建图(按层数建图)

{

int i;

memset(dis,-1,sizeof(dis));

dis[1]=0;

queue<int>q;

q.push(1);

while(!q.empty())

{

int k=q.front();

q.pop();

for(i=1;i<=n;i++)

{

if(flow[k][i]>0&&dis[i]<0)// 如果可以到达但还没有访问

{

dis[i]=dis[k]+1;

q.push(i);

}

}

}

if(dis[n]>0)

return 1;

else

return 0;

}

int dfs(int x,int mx)// 查找路径上的最小的流量

{

int i,a;

if(x==n) return mx;

for(i=1;i<=n;i++)

{

if(flow[x][i]&&dis[i]==dis[x]+1&&(a=dfs(i,min(mx,flow[x][i]))))

{

flow[x][i]-=a;

flow[i][x]+=a;

return a;

}

}

return 0;

}

int main()

{

int i,m,u,v,w,ans,res;

while(scanf("%d%d",&m,&n)!=EOF)

{

memset(flow,0,sizeof(flow));

for(i=0;i<m;i++)

{

scanf("%d%d%d",&u,&v,&w);

flow[u][v]+=w;

}

ans=0;

while(bfs())

{

while(res=dfs(1,inf))

ans+=res;

}

printf("%d\n",ans);

}

return 0;

}

dinic算法求最大流的学习的更多相关文章

  1. GraphCuts算法解析,Graphcuts算法求最大流,最小割实例

    图割论文大合集下载: http://download.csdn.net/detail/wangyaninglm/8292305 代码: /* graph.h */ /* Vladimir Kolmog ...

  2. Andrew算法求二维凸包-学习笔记

    凸包的概念 首先,引入凸包的概念: (有点窄的时候...图片右边可能会被吞,拉开图片看就可以了) 大概长这个样子: 那么,给定一些散点,如何快速地求出凸包呢(用在凸包上的点来表示凸包) Andrew算 ...

  3. HDU 1045 - Fire Net - [DFS][二分图最大匹配][匈牙利算法模板][最大流求二分图最大匹配]

    题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=1045 Time Limit: 2000/1000 MS (Java/Others) Mem ...

  4. 【网络流相关】最大流的Dinic算法实现

    Luogu P3376 于\(EK\)算法求最大流时每一次只求一条增广路,时间复杂度会比较高.尽管实际应用中表现比较优秀,但是有一些题目还是无法通过. 那么我们就会使用\(Dinic\)算法实现多路增 ...

  5. sw算法求最小割学习

    http://  blog.sina.com.cn/s/blog_700906660100v7vb.html 转载:http://www.cnblogs.com/ylfdrib/archive/201 ...

  6. Dinic算法——重述

        赛前赛后算是第三次接触Dinic算法了,每一次接触都能有种很好的感觉,直男的我没法描述~~ 已经比较懂得DInic的基本算法思想了 首先是bfs进行进行分层处理,然后dfs寻找分层后的最大流, ...

  7. 学习笔记 --- 最大流Dinic算法

    为与机房各位神犇同步,学习下网络流,百度一下发现竟然那么多做法,最后在两种算法中抉择,分别是Dinic和ISAP算法,问过 CA爷后得知其实效率上无异,所以决定跟随Charge的步伐学习Dinic,所 ...

  8. 求最大流dinic算法模板

    //最短增广路,Dinic算法 struct Edge { int from,to,cap,flow; };//弧度 void AddEdge(int from,int to,int cap) //增 ...

  9. POJ 1815 - Friendship - [拆点最大流求最小点割集][暴力枚举求升序割点] - [Dinic算法模板 - 邻接矩阵型]

    妖怪题目,做到现在:2017/8/19 - 1:41…… 不过想想还是值得的,至少邻接矩阵型的Dinic算法模板get√ 题目链接:http://poj.org/problem?id=1815 Tim ...

随机推荐

  1. qtree4

    https://zybuluo.com/ysner/note/1236834 题面 给出一棵边带权的节点数量为\(n\)的树,初始树上所有节点都是白色.有两种操作: 改变节点\(x\)的颜色,即白变黑 ...

  2. luoguP2939 [USACO09FEB]改造路Revamping Trails

    约翰一共有N)个牧场.由M条布满尘埃的小径连接.小径可 以双向通行.每天早上约翰从牧场1出发到牧场N去给奶牛检查身体. 通过每条小径都需要消耗一定的时间.约翰打算升级其中K条小径,使之成为高 速公路. ...

  3. openStack Packages yum upgrade

    依赖关系解决 ============================================================================================= ...

  4. JavaScript 中String和int互相转换

    在javascript里怎么样才能把int型转换成string型 (1) var num = 0;    a = x.toString();    (2) var x = 0;    a = x + ...

  5. MVVMLight消息通知实现机制详解(一)

    最近对委托.事件的订阅使用的太多,订阅与被订阅之间的绑定约束非常...麻烦,所以翻了下MVVMLight源码找出这段可以拿出来用的部分,详情见下: 一.开发中遇到的问题: 场景1:ClassA中存在事 ...

  6. PCB 内层负片散热PAD Symbols尺寸更改方法

    如下图这是我们熟悉的内层负片散热PAD Symbols,我们CAM制作时,为了满足PCB工厂生产制作能力,,会优化散热PAD尺寸,让热PAD的尺寸符合制作规范要求,通常我们只关注散热PAD的3个指标即 ...

  7. nodejs在windows下的安装

    Windowv 上安装Node.js Windows 安装包(.msi) : 32 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/node-v0.10.26-x ...

  8. [Swift通天遁地]五、高级扩展-(13)图片资源本地化设置:根据不同的语言环境显示不同语言版本图片

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  9. max_allowed_packet设置问题

    最近在运行的项目出现了一个线上事故,有人反映商城的东西下不了单了,到后台看了一下,果然报了一个错 Cause: com.mysql.jdbc.PacketTooBigException: Packet ...

  10. 【洛谷2624_BZOJ1005】[HNOI2008] 明明的烦恼(Prufer序列_高精度_组合数学)

    题目: 洛谷2624 分析: 本文中所有的 "树" 都是带标号的. 介绍一种把树变成一个序列的工具:Prufer 序列. 对于一棵 \(n\) 个结点的树,每次选出一个叶子(度数为 ...