前言

看到网上好多都用的链式前向星,就我在用 \(vector\) 邻接表……

定义

先来介绍一些相关的定义。(个人理解)

网络

一个网络是一张带权的有向图 \(G=(V,E)\) ,其中每任意一条边 \((u,v)\) 的权值称为这条边的容量 \(c(u,v)\) 。若这条边不存在,对应的容量就为 \(0\) 。其中包含两个特殊的点:源点 \(S\) 与汇点 \(T\) 。

流量

\(f\) 为网络的流函数,每一条边都有对应的流量。对于合法的流函数包含以下性质。

  1. 容量限制: \(f(u,v)≤c(u,v)\)
  2. 斜对称: \(f(u,v)=-f(v,u)\)
  3. 流量守恒:对于任意满足不为源点或汇点的节点 \(k\) ,有: \(∑_{u∈E}f(u,k)=∑_{v∈E}f(k,v)\)

最大流

对于一个网络,不难发现合法的流函数很多。这张图的流量为 \(∑_{k∈E}f(S,k)\) ,顾名思义,最大流就是这张网络的最大流量。

增广路

存在一条从 \(S\) 到 \(T\) 的路径,使得路径上所有的流量都不为 \(0\) ,则称该路径为增广路。

残量网络

对于任意时刻,当前时刻网络中,由所有结点与剩余容量大于 \(0\) 的边构成的该网络的子图被称为残量网络。

分层图

在这个算法中,满足层数 \(de[v]=de[u]+1\) 的边 \((u,v)\) ,所构成的子图称为分层图。

Dinic算法

建图

一条边中需要包含以下信息:终点节点编号,边的容量,相反的边的编号。

struct Node {
int to, value, rev;
Node() {}
Node(int T, int V, LL R) {
to = T;//节点编号
value = V;//边的容量
rev = R;//相反的边的编号
}
};

得双向存边,给出一条边 \((A,B)\) ,其长度为 \(C\) ,建一条从 \(A\) 到 \(B\) 的边,权值为 \(C\) ,与之相反的边权值为 \(0\)。

for(int i = 1; i <= m; i++) {
Quick_Read(A);
Quick_Read(B);
Quick_Read(C);
int idA = v[A].size(), idB = v[B].size();
v[A].push_back(Node(B, C, idB));
v[B].push_back(Node(A, 0, idA));
}

双向存边是为了给后面 \(dfs\) 时,若存在更优解,可以使得程序反悔,重新走另一条路。这里暂时不懂可以继续看后面的代码再来理解这样建图的意义。

主体部分

  • 一遍 \(bfs\) ,将残量网络构造成分层图,并求出当前残量网络是否存在增广路。
  • 一遍 \(dfs\) ,在该分层图中寻找增广路,将这条让这条增广路消失。

重复上述两个操作,直到当前网络中不存在增广路。

先来看 \(bfs\) 。其返回值为 \(bool\) ,意为该残量网络中是否还存在增广路。层数 \(de[i]\) 的意义很明白: \(S\) 到达当前的点 \(i\) 的最小步数。而按照这样的分层,每次只能将当前流量信息传递到下一层数节点上,可以很大程度上避免张冠李戴的情况。若 \(T\) 的层数为 \(1\) ,则说明当前 \(S\) 不能通向 \(T\) ,故而不存在增广路,跳出循环。

bool bfs_Dinic() {//bfs将残余网络分层,返回是否图中还存有增广路
memset(de, 0, sizeof(de));//清空深度
while(!q.empty())
q.pop();
q.push(s);
de[s] = 1; be[s] = 0;
while(!q.empty()) {
int now = q.front();
q.pop();
int SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
int next = v[now][i].to;
if(v[now][i].value && !de[next]) {
q.push(next);
be[next] = 0;//分层图改变,必须改变be[next]的值
de[next] = de[now] + 1;
if(next == t)
return true;
}
}
}
return false;
}

再来看 \(dfs\) ,来判断每一次的网络是否可以传递,完成增广的过程(以下代码附上注释)。这样一次走了不止 \(1\) 条增广路,节省了不少时间。

int dfs_Dinic(int now, int flow) {
if(now == t)//找到汇点
return flow;
int i, surp = flow;//当前剩余流量
int SIZ = v[now].size();
for(i = be[now]; i < SIZ && surp; i++) {
int next = v[now][i].to, valedge = v[now][i].value;
if(valedge && de[next] == de[now] + 1) {//&&前判断是否可以走,即是剩余流量是否为0;&&后判断是否满足当前残余网络分层要求
int maxnow = dfs_Dinic(next, Min(surp, valedge));
if(!maxnow)//经验定增广完毕,de这个点不需要在遍历了
de[next] = 0;
v[now][i].value -= maxnow;
v[next][v[now][i].rev].value += maxnow;//反悔,可能找到其他路径比当前这个流大
surp -= maxnow;
}
}
be[now] = i;//i之前的增广路已经更新完
return flow - surp;
}

最后在来说说剪枝, \(be\) 数组,在遍历 \(i\) 时,\(be[i]\) 之前的路径已经找完增广路了,而对于当前这个分层图,不存在会有更优解的情况,程序也不需要反悔,并不会影响程序的正确性,所以直接就不需要遍历之前的点。

时间复杂度

单看这段程序,可以发现时间复杂度为 \(O(n^2m)\) 。

int Dinic() {
int res = 0, flow = 0;
while(bfs_Dinic())
while(flow = dfs_Dinic(s, INF))//最大流的定义
res += flow;//流量守恒
return res;
}

而其实实际上并不需要这么多时间,参考资料得知可以处理\(10^4\)~\(10^5\)这样规模的网络。

C++代码

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
#define Min(a, b) ((a) < (b) ? (a) : (b))
void Quick_Read(LL &N) {
N = 0;
LL op = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-')
op = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
N = (N << 1) + (N << 3) + (c ^ 48);
c = getchar();
}
N *= op;
}
const LL MAXN = 2e2 + 5;
struct Node {
LL to, value, rev;
Node() {}
Node(LL T, LL V, LL R) {
to = T;
value = V;
rev = R;
}
};
vector<Node> v[MAXN];
queue<LL> q;
LL de[MAXN], be[MAXN];
LL n, m, s, t;
bool bfs_Dinic() {
memset(de, 0, sizeof(de));
while(!q.empty())
q.pop();
q.push(s);
de[s] = 1; be[s] = 0;
while(!q.empty()) {
LL now = q.front();
q.pop();
LL SIZ = v[now].size();
for(int i = 0; i < SIZ; i++) {
LL next = v[now][i].to;
if(v[now][i].value && !de[next]) {
q.push(next);
be[next] = 0;
de[next] = de[now] + 1;
if(next == t)
return true;
}
}
}
return false;
}
LL dfs_Dinic(LL now, LL flow) {
if(now == t)
return flow;
int i, surp = flow;
LL SIZ = v[now].size();
for(i = be[now]; i < SIZ && surp; i++) {
LL next = v[now][i].to, valedge = v[now][i].value;
if(valedge && de[next] == de[now] + 1) {
LL maxnow = dfs_Dinic(next, Min(surp, valedge));
if(!maxnow)
de[next] = 0;
v[now][i].value -= maxnow;
v[next][v[now][i].rev].value += maxnow;
surp -= maxnow;
}
}
be[now] = i;
return flow - surp;
}
LL Dinic() {
LL res = 0, flow = 0;
while(bfs_Dinic())
while(flow = dfs_Dinic(s, INF))
res += flow;
return res;
}
void Read() {
LL A, B, C;
Quick_Read(n);
Quick_Read(m);
Quick_Read(s);
Quick_Read(t);
for(int i = 1; i <= m; i++) {
Quick_Read(A);
Quick_Read(B);
Quick_Read(C);
LL idA = v[A].size(), idB = v[B].size();
v[A].push_back(Node(B, C, idB));
v[B].push_back(Node(A, 0, idA));
}
}
int main() {
Read();
printf("%lld", Dinic());
return 0;
}

看看自己做对没有吖

网络最大流 Dinic算法的更多相关文章

  1. P3376 【模板】网络最大流dinic算法

    P3376 [模板]网络最大流 题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点 ...

  2. 网络最大流Dinic

    1.什么是网络最大流 形象的来说,网络最大流其实就是这样一个生活化的问题:现在有一个由许多水管组成的水流系统,每一根管道都有自己的最大通过水流限制(流量),超过这个限制水管会爆(你麻麻就会来找你喝茶q ...

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

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

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

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

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

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

  6. 最大流——Dinic算法

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

  7. 求最大流dinic算法模板

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

  8. POJ 3469.Dual Core CPU 最大流dinic算法模板

    Dual Core CPU Time Limit: 15000MS   Memory Limit: 131072K Total Submissions: 24830   Accepted: 10756 ...

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

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

随机推荐

  1. 《Clojure编程》笔记 第4章 多线程和并发

    目录 背景简述 第4章 多线程和并发 4.0 我的问题 4.1 术语 4.1.1 一个必须要先确定的思考基础 4.2 计算在时间和空间内的转换 4.2.1 delay 4.2.2 future 4.2 ...

  2. STM32CubeIDE下载安装-GPIO基本配置操作-Debug调试(基于CMSIS DAP Debug)

    1.在ST官网下载STM32CubeIDE而不是STM32CubeMX,并且STM32CubeIDE是免费的.(STM32CubeIDE不支持中文路径,不然编译会出错) 2.如果你用的是keil开发环 ...

  3. 一款基于.NET Core的认证授权解决方案-葫芦藤1.0开源啦

    背景 18年公司准备在技术上进行转型,而公司技术团队是互相独立的,新技术的推动阻力很大.我们需要找到一个切入点.公司的项目很多,而各个系统之间又不互通,导致每套系统都有一套登录体系,给员工和客户都带来 ...

  4. [阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念

    [阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念 目录 [阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念 0x00 摘要 0x01 矩阵乘积 1.1 matmul pr ...

  5. Spring源码解析之BeanFactoryPostProcessor(一)

    BeanFactoryPostProcessor 在前面几个章节,笔者有介绍过BeanFactoryPostProcessor,在spring在解析BeanDefinition之后,正式初始化bean ...

  6. 深坑啊!同一个Spring AOP的坑,我一天踩了两次!

    GitHub 18k Star 的Java工程师成神之路,不来了解一下吗! GitHub 18k Star 的Java工程师成神之路,真的不来了解一下吗! GitHub 18k Star 的Java工 ...

  7. linux Netfilterr中扩展match target

    Match: netfilter定义了一个通用的match数据结构struct xt_match /* 每个struct xt_match代表一个扩展match,netfilter中各个扩展match ...

  8. MYSQL 存储引擎(面)

    存储引擎是MySQL的组件,用于处理不同表类型的SQL操作.不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能. 使用哪一种引擎可以灵活选择,一个数据 ...

  9. 科普干货|漫谈鸿蒙LiteOS-M与HUAWEI LiteOS内核的几大不同

    摘要:鸿蒙和LiteOS的内核都是一样的名字,可它们究竟有什么不同呢?一起来对比一下文件吧! HarmonyOS系统 HarmonyOS是一款"面向未来".面向全场景(移动办公.运 ...

  10. C函数 printf 拼接字符串

    C函数 printf 拼接字符串 从前学C语言,最常用的函数可能就是 printf 了,但是往往是这样: printf(年龄是:"%d",a); 由于不懂得怎么拼接字符串,有时候只 ...