网络流最大流——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) //增 ...
随机推荐
- 85道Java微服务面试题整理(助力2020面试)
微服务 面试题 1.您对微服务有何了解? 2.微服务架构有哪些优势? 3.微服务有哪些特点? 4.设计微服务的最佳实践是什么? 5.微服务架构如何运作? 6.微服务架构的优缺点是什么? 7.单片,SO ...
- Linux 误删catlina.out导致磁盘空间爆满,无法查询到大文件解决办法
大概是前俩天吧,发现公司的网站不定时的出现接口调不通的情况,便让手下小弟去服务器上查看一下,小弟告我磁盘空间满了,于是我让他处理一下.结果没想到他直接把 catlina.out 给干掉了.后果可想而知 ...
- yum管理及源码安装
一.配置YUM库及更新操作 yum概述 基于RPM包构建的软件更新机制,自动解决软件依赖关系 YUM仓库格式 本地:file:// 网络:ftp://或http:// yum源里面包含的内容 .rpm ...
- 用JavaScript完成页面自动操作
在之前的一篇<JavaScript实现按键精灵>中曾记录了几个事件对象,本文将会对它们进行一次实战,要完成的动作包括滚动.点击和翻页. 一.滚动 滚动是通过修改容器元素的scrollTop ...
- 【WPF学习】第三十九章 理解形状
在WPF用户界面中,绘制2D图形内容的最简单方法是使用形状(shape)——专门用于表示简单的直线.椭圆.矩形以及多变形的一些类.从技术角度看,形状就是所谓的绘图图元(primitive).可组合这些 ...
- DirectX11 With Windows SDK--30 图像模糊、索贝尔算子
前言 到这里计算着色器的主线学习基本结束,剩下的就是再补充两个有关图像处理方面的应用.这里面包含了龙书11的图像模糊,以及龙书12额外提到的Sobel算子进行边缘检测.主要内容源自于龙书12,项目源码 ...
- 吴sir 讲Python之——Pycharm的安装教程 (一)
欢迎您进入老吴的博客,如有联系请加QQ群:1055524279 首先在Pycharm的官网下载Pycharm Pycharm官网链接:https://www.jetbrains.com/pycharm ...
- ARTS Week 2
Nov 4,2019 ~ Nov 10,2019 Algorithm 本周主要的算法是如何求两个数的最大公因数.传统的想法便是对这两个数分解质因数,而后找到其公共因数,再相乘,这样就会得到最大公因数了 ...
- learn about sqlserver partitition and partition table --- add or remove table partitions addition more
Yes . In the previous. chapter , we see how to generate "partition function" "parttit ...
- JAVA中的约瑟夫环和猴子王问题
今天在书上(书名< java程序设计经典300例 >李源编著)看了一个有趣的问题,那就是java版的约瑟夫问题,想必大一的小伙伴们早就用c写过了吧 今天我在复习一下 首先问题是这样的n个人 ...