前言

网络流问题是一个很深奥的问题,对应也有许多很优秀的算法。但是本文只会讲述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. webpack入门系列2

    前面介绍了使用webpack做最基础的打包,接下来讲讲webpack的进阶. 1.使用 webpack 构建本地服务器: 想不想让你的浏览器监听你的代码的修改,并自动刷新显示修改后的结果,其实Webp ...

  2. java设计模式2————工厂模式

    1.工厂模式介绍: 1.1.实现了创建者与调用者的分离 1.2.详细分类: 简单工厂模式 工厂方法模式 抽象工厂模式 1.3.所遵循的OOP原则: 开闭原则:对扩展开放,对修改关闭 依赖倒转原则:面向 ...

  3. 把"重试"抽象出来做个工具类吧

    背景介绍 我们在工作中难免会写一些重复性的代码,所以需要我们具备一定的抽象能力,比如把共同的逻辑抽取到抽象类中,也可以通过一些工具类来避免冗余代码 今天这篇文章就是把一个调用服务的重试功能抽取出一个工 ...

  4. c++ 中数组的引用

    在C++里,数组也是可以引用的. 代码如下: char str1[] = "abcde"; ] = str1; 解读第二句代码,括号的优先级最高,'str2'首先与'&'相 ...

  5. vue 入门 ------简单购物车功能实现(全选,数量加减,价格加减)

    简易购物车功能(无任何布局 主要是功能) 数量的加减 商品的总价钱 全选与全不选 删除(全选.价格 受影响) <script src="https://cdn.jsdelivr.net ...

  6. OSPF笔记——LSA及其字段,及其作用

    Link State ID Link State ID remains at 32 bits in length, Link State ID has shed any addressing sema ...

  7. tomcat 多端口 多容器 多域名 配置

    先强调一个原则: server下面可以有多个service,用于配置不同监听端口 service下面可以有多个Host,用于配置该端口下的不同域名 Host里可以包含多个Context,用于配置该端口 ...

  8. Fastdfs php扩展访问

    一.安装FastDFS client php extension compiled under PHP 5.4 and PHP 7.0   1.安装php扩展,进入fastdfs源码文件夹中的  ph ...

  9. centos6.5和centos7.5统一字符集为zh_CN.UTF-8解决系统和MySQL数据库乱码问题

    linux的服务器需要做的操作 centos6.5下: 修改默认字符集为 zh_CN.UTF-8,如果没有中文语言包可能需要安装中文语言包支持 [root@meinv01 ~]# yum groupi ...

  10. Spring-cloud微服务实战【九】:分布式配置中心config

      回忆一下,在前面的文章中,我们使用了spring cloud eureka/ribbon/feign/hystrix/zuul搭建了一个完整的微服务系统,不管是队内还是对外都已经比较完善了,那我们 ...