前言

网络流问题是一个很深奥的问题,对应也有许多很优秀的算法。但是本文只会讲述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算法的更多相关文章

  1. [讲解]网络流最大流dinic算法

    网络流最大流算法dinic ps:本文章不适合萌新,我写这个主要是为了复习一些细节,概念介绍比较模糊,建议多刷题去理解 例题:codevs草地排水,方格取数 [抒情一下] 虽然老师说这个多半不考,但是 ...

  2. Power Network(网络流最大流 & dinic算法 + 优化)

    Power Network Time Limit: 2000MS   Memory Limit: 32768K Total Submissions: 24019   Accepted: 12540 D ...

  3. 网络流——最大流Dinic算法

    前言 突然发现到了新的一年什么东西好像就都不会了凉凉 算法步骤 建残量网络图 在残量网络图上跑增广路 重复1直到没有增广路(注意一个残量网络图要尽量把价值都用完,不然会浪费建图的时间) 代码实现 #i ...

  4. 网络流之最大流Dinic算法模版

    /* 网络流之最大流Dinic算法模版 */ #include <cstring> #include <cstdio> #include <queue> using ...

  5. 网络流(最大流-Dinic算法)

    摘自https://www.cnblogs.com/SYCstudio/p/7260613.html 网络流定义 在图论中,网络流(Network flow)是指在一个每条边都有容量(Capacity ...

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

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

  7. hdu-3572 Task Schedule---最大流判断满流+dinic算法

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3572 题目大意: 给N个任务,M台机器.每个任务有最早才能开始做的时间S,deadline E,和持 ...

  8. 最大流——Dinic算法

    前面花了很长时间弄明白了压入-重标记的各种方法,结果号称是O(V3)的算法测demo的时候居然TLE了一个点,看了题解发现所有人都是用Dinic算法写的,但它的复杂度O(V2E)明显高于前者,具体是怎 ...

  9. 求最大流dinic算法模板

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

随机推荐

  1. 一个支持 CodeFirst/DbFirst/ModelFirst 的数据库小工具

    一个支持 CodeFirst/DbFirst/ModelFirst 的数据库小工具 Intro DbTool 是一个支持 CodeFirst/DbFirst/ModelFirst 的数据库小工具,原本 ...

  2. POJ_1221_DP

    http://poj.org/problem?id=1221 简单dp,dp[i][j]表示i被划分成首位>=j的方案数. dp[i][i]为1,i为偶数时dp[i][i/2]为2. 剩下的可以 ...

  3. java9循环结构进阶

    public class jh_01_循环嵌套 { public static void main(String[] args) { // for(int i = 1;i<= 5;i++) { ...

  4. HTML5与HTML4的区别-----文档结构

    HTML5在结构和语法上做了大量的简化.当然,也提供了语义化的标签 结构上区别: 1.简化了文档声明语句     HTML5仅规定了一种:       <!DOCTYPE html> 2. ...

  5. jquery deferred 转载

    阮一峰的网络日志 » 首页 » 档案 JavaScript http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquer ...

  6. Standby Redo Logs的前世今生与最佳实践

    编辑手记:使用过Data Guard的人应该对于Standby Redo Logs都不陌生,在配置了 Standby Redo Logs的standby中,能够进行日志的实时应用,同时Standby ...

  7. WebAPI 微信小程序的授权登录以及实现

    这个星期最开始 ,老大扔了2个任务过来,这个是其中之一.下面直接说步骤: 1.  查阅微信开发文档  https://developers.weixin.qq.com/miniprogram/dev/ ...

  8. 用赋值表达式作为bool值

    enum Status { stOk, stQuit, stError }; int main() { Status status; int n; bool b1 = (status = stOk); ...

  9. Yandex Big Data Essentials Week1 Unix Command Line Interface File System exploration

    File System Function In computing, a file system or filesystem is used to control how data is stored ...

  10. 浅谈JSONP 的工作原理

    小编最近在工作中经常用到 jsonp 这个东西, 表示之前从来没用过  最近稍微研究了下 当然很多内容来源于网上 收集整理 你懂的 ~~~ 话说我们访问一个页面的时候 需要像另一个网站获取部分信息, ...