网络流最大流——dinic算法
前言
网络流问题是一个很深奥的问题,对应也有许多很优秀的算法。但是本文只会讲述dinic算法
最近写了好多网络流的题目,想想看还是写一篇来总结一下网络流和dinic算法以免以后自己忘了。。。
网络流问题简述
一个很普遍的例子就是——你家和自来水厂之间有许多中转站,中转站又由一些水管连接着。我们假设自来水厂的供水是无限的,并且中转站内能存储的水量也是无限的,但是管道有宽又窄,很显然管道内的流量必须小于等于管道的承载范围(否则管道就被撑爆了),那么问题就是要你求出你家最多能收到多大流量的水。
emmm可能语言还是不好描述(个人表达能力太差),还是给一道模板题吧。。。
https://www.luogu.com.cn/problem/P3376
用于解决网络流的算法有很多,EK,HLPP,dinic,ISAP......本文只讨论dinic算法(还不是因为我太菜了写不出来HLPP)
基本概念
基本概念还是要提一下的
源点:简单来说就是自来水厂
汇点:简单来说就是你家
容量:就是水管的承载范围,我们用c(u,v) (capacity)来表示
流量:此时水管里面水的流量,我们用f(u,v) (flow)来表示
弧:网络流中对有向边的简称
网络流图:有向图G=(V,E)中,有唯一的远点S,y有唯一的汇点T,并且每一条弧的容量都非负
可行流:对于弧(u,v)来说,f(u,v)<=c(u,v),我们就称f(u,v)是可行流,意思就是流量<=容量,否则水管就被撑爆了
前向弧:从u指向v的一条有向边
反向弧:从v指向u的一条有向边
增广路:从S到T的一条简单路径,并且路径上的任意弧都是可行流,反向弧f(u,v)=0
暂时就说这么多吧。。。
肯定还是一脸懵逼的(反正一般我看到这里都会懵逼的),那么现在正式开始介绍dinic算法
网络流算法
最经典的网络流算法通过不停地寻找增广路来计算最大流。还是非常好理解的,因为水会流到它能流到的任何地方,而水从源点流到汇点不就是一种增广吗。但是我们会遇到一个问题:

对于这副图来说,它的最大流很明显肯定是2,因为我们走1->2->4和1->3->4这两条增广路时每条增广路的流量都是1,所以最终算出来的总和是2。但是如果我们第一次就走1->2->3->4这条增广路的话,我们会发现,我们接下来无法在图上继续增广。因为仅凭1->3,2->4这两条弧不能让1和4连通。
所以,我们要做的就是为每一条边建立一个反边,用来使某些增广路“反悔”到它一开始的地方
由于反边并不是一条实际模型里面存在的边,所以初始化的话就是c'(u,v)=c(u,v),f'(u,v)=0
像这样,蓝色的是正常弧,红(橙?)色的是反向弧:

那么对于一条已经增广了的路径来说,我们怎么反悔呢?
当然使给目前弧容量-这条增广路流量,反向弧加上这条增广路流量,这样的话首先这条增广路一定不可能从起点再流过一次一摸一样的了,因为对于任意增广路(S,T)来说,它的最大流量肯定等于这条路所经过的弧(u,v)中c(u,v)最小的那条弧的c(u,v) (好绕口),而修改之后这条增广路上肯定有容量为0的弧存在,所以对于从S到T的某一条增广路来说,这条增广路就可以形成一个"反悔"的模型。
顺便说一句,如果说对于(u,v)来说(v,u)是反向弧的话,那么对于(v,u)来说(u,v)也是反向弧,所以反向弧是相对的
放一组图看一下
对于之前那幅图,我们跑完1->2->3->4这条增广路时整幅图被我们修改成这个样子:

此时,我们还可以走另外一条增广路:1->3->2->4,结果就是这样的:

(之前图放错了所以又重新画了一张,有一点不一样应该不影响阅读吧。。。。。。)
此时,我们起点S的出度虽然是2,但是这两条出边的容量都变成0了,所以整个图就跑完了网络流,答案最后算的是2,
这就是著名的Edmond-Karp算法,简称EK算法
所以说dinic呢???
不急,我们还没说完。。。
EK算法固然有用,但是它可能会被一些特殊数据卡住:

这里的inf只是一个极大值而已 (比如19260817之类的)
那么比如说我们首先增广1->2->3->4这条路径,就会变成这样:

接着我们增广1->3->2->4:

我们会发现,对于这样一个简单的图,我们需要增广2*inf-1次才能完成计算,时间消耗太大,所以我们引入一个方案——给每个点一个“高度”,对于上面的例子,如果我们选择1->2->4或者是1->3->4就不会被卡了,但是我们选择的1->2->3->4,而如果我们通过BFS处理出每个点的“高度”的化,我们会发现你,2和3都是在同一高度的,也就是说弧(2,3)相当于把两条从S到T的最短路给连接起来了。如果我们规定,对于点u,它能增广到的下一个点v必须满足这个条件:depth[v]=dep[u]+1,这样我们就会避免经过某一些把两条最短路给连接起来的边。这里说一下,因为网络流里面边的权值是流量而不是长度,所以从S到u的最短路就是u在图里面的深度,可以意会一下。
又到了喜闻乐见的代码时间
首先我们先声明结构体(我比较喜欢的是vector邻接表建图):
struct edge{
int to,flow,rev;
edge(int to_,int flow_,int rev_){
to=to_;
flow=flow_;
rev=rev_;
}
};
vector<edge> gpe[maxn];
这里的rev是反边的位置,下面会解释,我们接下来继续看
为图分层的BFS:
int dep[maxn];
bool bfs(int st,int ed) {
memset(dep, 0, sizeof dep);
queue<int> q;
q.push(st);
dep[st] = 1;
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i=0;i<gpe[u].size();i++) {
int v = gpe[u][i].to;
if( dep[v] || gpe[u][i].flow <= 0) continue;
dep[v] = dep[u] + 1;
q.push(v);
}
}
return dep[ed];
}
也不是很难,唯一要注意的就是要在判断是否取v的时候特殊判一下是否下一条边的容量已经是0了,如果是0就没办法继续。其他的部分就是一个裸的BFS分层,没什么好讲的
接下来是dfs增广:
int dfs(int u,int flow,int ed){
if(u == ed) return flow;
int add = 0;
for(int i=0;i<gpe[u].size();i++) {
int v = gpe[u][i].to;
if(dep[v] != dep[u] + 1) continue;
if(!gpe[u][i].flow) continue;
int tmpadd = dfs(v, min(gpe[u][i].flow, flow - add),ed);
gpe[u][i].flow -= tmpadd;
gpe[v][gpe[u][i].rev].flow+=tmpadd;
add += tmpadd;
}
return add;
}
最后是dinic函数:
int dinic(int st,int ed){
int max_flow = 0;
while (bfs(st,ed)){
max_flow += dfs(st,inf,ed);
}
return max_flow;
}
然后是加边:
void addedge(int u,int v,int w){
gpe[u].push_back(edge(v,w,gpe[v].size()));
gpe[v].push_back(edge(u,0,gpe[u].size()-1));
}
这里要看一下,我们反边的意义就是,假如我们现在站在u点,我们指定了g[u][x]这条边进行访问,但是我们同时要得到这个点的反边。虽然我们从u点看是第x条边,但是从v点看却不一定是x,所以这里我们动态计算从v点看的相对位置,具体原理大家可以手动模拟一下 (没有什么问题是手动模拟搞不定的)
总代码(之前那到模板题的代码):
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int inf=0x7f;
struct edge{
int to,flow,rev;
edge(int to_,int flow_,int rev_){
to=to_;
flow=flow_;
rev=rev_;
}
};
vector<edge> gpe[maxn];
int dep[maxn];
bool vis[maxn];
bool bfs(int st,int ed) {
memset(dep, 0, sizeof dep);
queue<int> q;
q.push(st);
dep[st] = 1;
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i=0;i<gpe[u].size();i++) {
int v = gpe[u][i].to;
if( dep[v] || gpe[u][i].flow <= 0) continue;
dep[v] = dep[u] + 1;
q.push(v);
}
}
return dep[ed];
}
int dfs(int u,int flow,int ed){
if(u == ed) return flow;
int add = 0;
for(int i=0;i<gpe[u].size();i++) {
int v = gpe[u][i].to;
if(dep[v] != dep[u] + 1) continue;
if(!gpe[u][i].flow) continue;
int tmpadd = dfs(v, min(gpe[u][i].flow, flow - add),ed);
gpe[u][i].flow -= tmpadd;
gpe[v][gpe[u][i].rev].flow+=tmpadd;
add += tmpadd;
}
return add;
}
int dinic(int st,int ed){
int max_flow = 0;
while (bfs(st,ed)){
max_flow += dfs(st,inf,ed);
}
return max_flow;
}
void addedge(int u,int v,int w){
gpe[u].push_back(edge(v,w,gpe[v].size()));
gpe[v].push_back(edge(u,0,gpe[u].size()-1));
}
int main(void){
int n,m,s,t;
scanf("%d %d %d %d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
addedge(u,v,w);
}
printf("%d",dinic(s,t));
}
总结
虽然网络流是一个很复杂的东西,但是目前来看在竞赛方面网络流题难点还是建模方面(玄学拆点,玄学连边),并且还有很多的题目表面上看起来和网络流八竿子打不着,但是最终却奇迹般的用网络流解决了。。。 所以做网络流题目一定要有一些经验(一些相关定理)和拆点连边的感觉,仔细分析题意,建模,(然后随便跑一个板子交上去肯定能AC)。
网络流最大流——dinic算法的更多相关文章
- [讲解]网络流最大流dinic算法
网络流最大流算法dinic ps:本文章不适合萌新,我写这个主要是为了复习一些细节,概念介绍比较模糊,建议多刷题去理解 例题:codevs草地排水,方格取数 [抒情一下] 虽然老师说这个多半不考,但是 ...
- Power Network(网络流最大流 & dinic算法 + 优化)
Power Network Time Limit: 2000MS Memory Limit: 32768K Total Submissions: 24019 Accepted: 12540 D ...
- 网络流——最大流Dinic算法
前言 突然发现到了新的一年什么东西好像就都不会了凉凉 算法步骤 建残量网络图 在残量网络图上跑增广路 重复1直到没有增广路(注意一个残量网络图要尽量把价值都用完,不然会浪费建图的时间) 代码实现 #i ...
- 网络流之最大流Dinic算法模版
/* 网络流之最大流Dinic算法模版 */ #include <cstring> #include <cstdio> #include <queue> using ...
- 网络流(最大流-Dinic算法)
摘自https://www.cnblogs.com/SYCstudio/p/7260613.html 网络流定义 在图论中,网络流(Network flow)是指在一个每条边都有容量(Capacity ...
- 学习笔记 --- 最大流Dinic算法
为与机房各位神犇同步,学习下网络流,百度一下发现竟然那么多做法,最后在两种算法中抉择,分别是Dinic和ISAP算法,问过 CA爷后得知其实效率上无异,所以决定跟随Charge的步伐学习Dinic,所 ...
- hdu-3572 Task Schedule---最大流判断满流+dinic算法
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3572 题目大意: 给N个任务,M台机器.每个任务有最早才能开始做的时间S,deadline E,和持 ...
- 最大流——Dinic算法
前面花了很长时间弄明白了压入-重标记的各种方法,结果号称是O(V3)的算法测demo的时候居然TLE了一个点,看了题解发现所有人都是用Dinic算法写的,但它的复杂度O(V2E)明显高于前者,具体是怎 ...
- 求最大流dinic算法模板
//最短增广路,Dinic算法 struct Edge { int from,to,cap,flow; };//弧度 void AddEdge(int from,int to,int cap) //增 ...
随机推荐
- 7.场景5:使用Linux桥的VRRP(L3HA)的高可用性
此场景描述了使用ML2插件和Linux网桥的OpenStack网络服务的高可用性实现. 他的高可用性实施例增强了这样的场景:具有Linux网桥架构的传统使用了keepalived的虚拟路由器冗余协议( ...
- Java类加载器和双亲委派机制
前言 之前详细介绍了Java类的整个加载过程(类加载机制详解).虽然,篇幅较长,但是也不要被内容吓到了,其实每个阶段都可以用一句话来概括. 1)加载:查找并加载类的二进制字节流数据. 2)验证:保证被 ...
- Spring 依赖注入原理
所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中.当spring容器启动后,spring容器初始化,创建并管理bean对象,以及销毁它.所以我们只需从容器直接获取Bean对象就行, ...
- QT笔记:数据库总结
http://blog.csdn.net/reborntercel/article/details/6991147 #include <QtSql> QT += sql QSqlDatab ...
- python学习记录(六)
0903--https://www.cnblogs.com/fnng/archive/2013/04/21/3034442.html 基本语句的用法 使用逗号输出(想要同时输出文本和变量值,又不希望使 ...
- Codeforces_723_B
http://codeforces.com/problemset/problem/723/B 求括号内单词数和括号外最大单词长度,注意细心,尤其是ok和sum的置0. #include<iost ...
- ZOJ 4067 Books (2018icpc青岛J) (贪心)
题意 给你一个长度为n的数组,代表每一个物品的价格.你有一个初始钱数\(x\),采用以下方法贪心: 从\(1\)到\(n\)扫一遍,如果\(x\)不比\(a[i]\)小,就买下它,买不起就跳过. 给你 ...
- T1飞跃树林 && 【最长等差子序列】
solution by Mr.gtf 一道简单的递推 首先我们对树高从大到小排序 很容易得到递推式 ans[i]=Σans[j] (j<i && h[j]-h[i]<=K) ...
- 大话IDL编程之函数功能调用(envi_doit、ENVIRaster、ENVITask)
2020年2月1日.好长时间没更新博客,还真有点不习惯.受新型冠性病毒的影响,平时街上熙熙攘攘的人流了无踪影,2020的春节竟然来的如此冷清.为响应“呆在家里就是做贡献的号召”,在家一宅就是十多天.闲 ...
- C++括号匹配检测(用栈)
输入一串括号,包括圆括号和方括号,()[],判断是否匹配,即([]())或[([][])]为匹配的正确的格式,[(])或([())为不匹配的格式. #include<iostream> # ...