初探网络流:dinic/EK算法学习笔记
前记
这些是初一暑假的事:
“都快初二了,连网络流都不会,你好菜啊!!!” from 某机房大佬 to 蒟蒻我。
flag:——NOIP后要学网络流
咕咕咕……………………………………………………………………………………
现在是2018-12-29,我终于开始学网络流了。
网络流其实也没有想象的那么难啊,最主要就是要明白怎么建图!!!其他什么的板子一套就完事了。
正式学习
what is 网络流
相信大家都听过网络流这个名字。哦?你知道网络,还知道输入输出流?呀,看来你已经会网络流了啊!
嗯,开个玩笑
网络流(network-flows)是一种图论算法,说起来可能比较抽象,不过我们可以把网络图想象成一个水管分布图,网络流就相当于水流。边就是水管,节点就是一个转换水流的地方。

就像上面的图,它其实就是网(shui)络(guan)图,当然,流入你家的可能不止一根水管,那么,如果现在让你分配水怎么流,就是你可以控制水到了某一个点后往哪根水管流,怎么使流入你家的水最多,就是最大流问题。
另外最小割是什么呢?
就是如果有恐怖分子要拆水管,那么他割宽度为5的水管就要相应耗费5单位的力,所以他想耗尽量少的力去让你家没有水,那么他最终耗费的最小的力就是最小割。
网络流的基本性质
1、流量平衡:即 出流的量=入流的量 ,这个比较好理解,就是说如果你入流了100单位水,可水管只能流出50单位水,那么那另外50单位水就爆水管了。
2、最小割=最大流:如果恐怖分子知道你怎么分配使你家的流量最大,那么他就可以把所有水流的路径中最小的割了,那其实就是最大流其中一条路径所能提供的水量。
算法
EK
EK是基于FF算法的一个改良版算法,由于FF算法实在是太废了,我在这里不介绍FF算法,直接讲EK,反正他们的思想差不多。
那么EK算法的思路是什么呢?其实就是不断增广,直到不能再增广为止。那么什么是增广呢,有可能有些人不是很明白?其实增广就是更新,我也一直把增广理解为更新,对一条路进行增广,那条路就是增广路。
额,可能还是不懂吧。其实就是,呐,从源点到汇点不是有很多条路径嘛。EK算法就是每一次抽一条路径,在这条路径上找边权最小值,即这条路径的流量。然后把这条路径的所有边权都减去这个最小值。直到最后没有任何路径可以给汇点输送流量为止。如果一条路径有0容量的边,那么那条路径就废了,因为最多只能流0容量的流量,和不流没区别。
那么我们想一下怎么实现EK算法呢?每一次都进行更新,感觉和搜索有点联系,每一次都向下一个节点更新,直到不能更新就回溯。嗯,那可以写dfs!!!
明显写dfs来跑是非常慢的,这就是FF算法的实现思想。那么EK是什么呢,其实就是把dfs换成了bfs。
那么怎么保证EK的正确性呢?事实上,如果普通建边是跑不了最大流的,EK支持一个反悔操作,什么是反悔呢?不要以为是太高级,其实就是从u走到v,我还可以又从v走回u。我们也可以这么理解,之前有一条路径不可以形成最大流,那么我们就将那条路径反悔,换其他路径。
实现这个反悔操作只需要建反向边,反向边刚开始的流量为0,因为暂时不能往回流,如果流过一条边,那条边的容量自然减少,而反向边容量增加,那么之后反向边就可以流动了。

那么来看看EK的具体实现吧:
//网络最大流(EK)
#include<bits/stdc++.h>
#define maxn 1000001
#define INF 119260817
using namespace std;
int cnt,cost[maxn],from[maxn],to[maxn],Next[maxn],head[maxn],consume[maxn];
int dis[maxn],vis[maxn],flow[maxn],last[maxn],maxflow,check;
queue<int>q;
int S,T,n,m;
void add(int x,int y,int z){ //建边
++cnt;cost[cnt]=z;
from[cnt]=x;to[cnt]=y;
Next[cnt]=head[x];head[x]=cnt;
}
bool bfs(int S,int T){ //EK核心代码BFS
for(int i=1;i<=n;i++)last[i]=0,vis[i]=-1;
q.push(S);dis[S]=0;vis[S]=1;flow[S]=INF;
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i!=-1;i=Next[i]){
int v=to[i];
if(cost[i]&&vis[v]==-1){ //发现有路径可以增广
flow[v]=min(flow[u],cost[i]); //流量更新
last[v]=i; //为了最后进行该路径所有边权减最小值,标记来边
q.push(v);
vis[v]=u;
}
}
}if(vis[T]!=-1)return true; //如果汇点没有被更新,那么就结束EK
return false;
}
void update(int S,int T){ //对求出的路径进行更新,即对BFS求出的路径进行减最小值更新
int now=T;
while(now!=S){
int i=last[now];
cost[i]-=flow[T];cost[i^1]+=flow[T]; //记得给反向边增加
now=from[i]; //进行下一条边
}
maxflow+=flow[T]; //最大流加上流量
}
void EK(){
maxflow=0;
while(bfs(S,T)==true){
update(S,T);
}
}
int main(){cnt=1;
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&n,&m,&S,&T);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,0); //刚开始要建反向边,容量是0
}
EK();
printf("%d",maxflow);
}
EK时间复杂度:O(n m^2).
dinic
在学dinic之前,希望大家先明白EK的实现思路和原理。讲dinic会在EK的基础上讲的。
其实dinic和EK差不多,只不过dinic多了个分层,这个所谓的分层是一个限制,EK在bfs不是只要有路走就增广吗?而dinic则限制只能往下一层走。
那么来讲讲dinic怎么实现吧:
首先是分层,dinic分层用bfs来分层,这个没什么好说的,就是源点是第0层,和源点有边的点是第1层,这样bfs下去分层。当然,在分层中还可以判断汇点还有没有流可以汇入,如果汇点分不了层了,就代表没有流可以流入汇点了。
然后是找最大流,dinic的找最大流是用dfs的。具体就是从源点找到满足的点就一直递归下去,直到找到汇点,返回中途的最小值,然后更新边权,和EK一样,不过dinic是递归形式,所以可以直接在dfs上更新,不需要在写个update操作。

下面看看dinic的实现:
//网络最大流dinic
#include<bits/stdc++.h>
#define maxn 1000001
#define INF 19260817
using namespace std;
int cnt,cost[maxn],from[maxn],to[maxn],Next[maxn],head[maxn];
int level[maxn];
queue<int>q;
int S,T,n,m;
void add(int x,int y,int z){ //建边
++cnt;cost[cnt]=z;
from[cnt]=x;to[cnt]=y;
Next[cnt]=head[x];head[x]=cnt;
}
bool bfs(){ //bfs分层
memset(level,-1,sizeof(level));
level[S]=0;q.push(S);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=Next[i]){
int v=to[i];
if(cost[i]!=0&&level[v]==-1){ //如果容量是0||已被更新就不更新了
level[v]=level[u]+1;
q.push(v);
}
}
}if(level[T]!=-1)return true; //如果流不动了就结束dinic
return false;
}
int dfs(int u,int flow){ //dfs找最大流
if(u==T)return flow;
int ret=flow; //记录初始流量
for(int i=head[u];i!=-1;i=Next[i]){
if(ret<=0)break; //如果已经没流了就退出
int v=to[i];
if(cost[i]!=0&&level[u]+1==level[v]){
int k=dfs(v,min(cost[i],ret)); //把能流的都给下一个点
ret-=k;cost[i]-=k;cost[i^1]+=k; //边权更新,剩余流量更新
}
}
return flow-ret; //返回流出的流量
}
int dinic(){
int ans=0;
while(bfs()==true){
ans+=dfs(S,INF); //累加最大流
}
return ans;
}
int main(){cnt=1;
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&n,&m,&S,&T);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,0); //EK一样建边
}
printf("%d",dinic());
}
dinic时间复杂度:O(n^2 m).
希望大家也能学会网络流哦!!!
谢谢观赏,点个赞吧!
初探网络流:dinic/EK算法学习笔记的更多相关文章
- 某科学的PID算法学习笔记
最近,在某社团的要求下,自学了PID算法.学完后,深切地感受到PID算法之强大.PID算法应用广泛,比如加热器.平衡车.无人机等等,是自动控制理论中比较容易理解但十分重要的算法. 下面是博主学习过程中 ...
- C / C++算法学习笔记(8)-SHELL排序
原始地址:C / C++算法学习笔记(8)-SHELL排序 基本思想 先取一个小于n的整数d1作为第一个增量(gap),把文件的全部记录分成d1个组.所有距离为dl的倍数的记录放在同一个组中.先在各组 ...
- Manacher算法学习笔记 | LeetCode#5
Manacher算法学习笔记 DECLARATION 引用来源:https://www.cnblogs.com/grandyang/p/4475985.html CONTENT 用途:寻找一个字符串的 ...
- Johnson算法学习笔记
\(Johnson\)算法学习笔记. 在最短路的学习中,我们曾学习了三种最短路的算法,\(Bellman-Ford\)算法及其队列优化\(SPFA\)算法,\(Dijkstra\)算法.这些算法可以快 ...
- Johnson 全源最短路径算法学习笔记
Johnson 全源最短路径算法学习笔记 如果你希望得到带互动的极简文字体验,请点这里 我们来学习johnson Johnson 算法是一种在边加权有向图中找到所有顶点对之间最短路径的方法.它允许一些 ...
- 【网络流】EK算法及其优化
今天上午我仿佛知道了什么叫做网络流,这里推荐一篇博客,大家入门网络流的可以看一下这篇博客,保证一看就懂! 博客链接: 网络流入门 这里有一篇经过我改过的EK带注释代码(博客里也有一样的,只是加了一些注 ...
- ACM/ICPC 之 网络流入门-EK算法(参考模板)(POJ1273)
基于残留网络与FF算法的改进-EK算法,核心是将一条边的单向残留容量的减少看做反向残留流量的增加. //网络流 //EK算法 //Time:16Ms Memory:348K #include<i ...
- 算法学习笔记——sort 和 qsort 提供的快速排序
这里存放的是笔者在学习算法和数据结构时相关的学习笔记,记录了笔者通过网络和书籍资料中学习到的知识点和技巧,在供自己学习和反思的同时为有需要的人提供一定的思路和帮助. 从排序开始 基本的排序算法包括冒泡 ...
- R语言实现关联规则与推荐算法(学习笔记)
R语言实现关联规则 笔者前言:以前在网上遇到很多很好的关联规则的案例,最近看到一个更好的,于是便学习一下,写个学习笔记. 1 1 0 0 2 1 1 0 0 3 1 1 0 1 4 0 0 0 0 5 ...
随机推荐
- http的长连接和短连接(史上最通俗!)
1.以前的误解 很久之前就听说过长连接的说法,而且还知道HTTP1.0协议不支持长连接,从HTTP1.1协议以后,连接默认都是长连接.但终究觉得对于长连接一直懵懵懂懂的,有种抓不到关键点的感觉. 今天 ...
- C语言:求n(n<10000)以内的所有四叶玫瑰数。-将字符串s1和s2合并形成新的字符串s3,先取出1的第一个字符放入3,再取出2的第一个字符放入3,
//函数fun功能:求n(n<10000)以内的所有四叶玫瑰数并逐个存放到result所指数组中,个数作为返回值.如果一个4位整数等于其各个位数字的4次方之和,则称该数为函数返回值. #incl ...
- windows server 2016系统安装
- 使用SQL计算宝宝每次吃奶的时间间隔(数据保障篇)
目前程序从功能上其实已经完全满足客户(当然我这里的客户都是指媳妇儿^_^)需求,具体可参考: 使用SQL计算宝宝每次吃奶的时间间隔 使用SQL计算宝宝每次吃奶的时间间隔(续) 那么本篇 使用SQL计算 ...
- 记C++中发现的隐式转换问题
#include <iostream> #include <string> using std::cin; using std::cout; using std::endl; ...
- 例题3_4 猜数字游戏的提示(UVa340)
实现一个经典“猜数字”游戏.给定答案序列和用户猜的序列,统计有多少数字位置正确(A),有多少数字在两个序列都出现过但位置不对(B). 输入包含多组数据.每组输入第一行为序列长度n,第二行是答案序列,接 ...
- 基于docker的以太坊集群的私有链开发环境
转载博文:https://www.jianshu.com/p/8af386ec5f9e https://www.jianshu.com/p/7994db7a2b89?from=singlemessag ...
- IPSec的高可用性技术
IPSec VPN的高可用性技术:①.DPD(Dead Peer Detection)对等体检测 ——旨在检查有问题的IPSec VPN网络,并快速的切换到备 ...
- Codeforces Round #620 (Div. 2) C. Air Conditioner
Gildong owns a bulgogi restaurant. The restaurant has a lot of customers, so many of them like to ma ...
- XC1263 签到题(哇 ,写得我怀疑人生啊!!!@!@)
1263: 签到题 时间限制: 1 Sec 内存限制: 128 MB提交: 174 解决: 17 标签提交统计讨论版 题目描述 大家刚过完寒假,肯定还没有进入状态,特意出了一道签到题给各位dala ...