网络流初步浅谈:EK与Dinic
网络流这么美妙的东西
肯定是要好好学它的
主要还是为了搞这个二分最大匹配去学的
匈牙利算法不太适合我,就来进阶一下(
先来看最大流
最大流是解决从源点到汇点的最大流量,这是个网络流中几乎最基础的问题。
就像图论里面的最短路一样。
而我们的目的就是从
V个点,
E条有向边,
每个边有c(u,v)的容量,
解决这个抽水马桶最多能泵出多少水的问题。
这里有几个名词要解释一下:
- 网络:一个有向图G=(V,E),有两个特殊节点:源点和汇点。
- 边的流量:每条有向边(x,y)\(\in\)E都有一个权值c(x,y),称为边的流量。若(x,y)\(\notin\)E,则c(x,y)=0.
- 最大流:从源点到汇点的最大流量。
- 增广路:一条源点到汇点的所有边的剩余容量$\ge$0的路径
- 残留网:在找到一条增广路之后,对这条增广路的所有边建立反向边。这样的话,在正向边被前面的增广路堵死的情况下,可以通过反向边“TD退流”。
- 可行流应满足:
- 1.容量限制:\(f(x,y)\le c(x,y\)
- 2.流量限制:\(\sum_{(u,x)\in E} f(u,x)=\sum_{(x,v)\in E}f(x,v),x\ne S,x\ne T\)
- 而\(\sum_{(S,v)\in E}f(S,v)\)称为整个网络的流量
我们很快就能想象到:
数据像洪水一样从S(源点)填充进边里
然后最后又汇聚到T(汇点)
被我们查询到最大流是多少
然后结束
这样,我们就想到了解决最大流的第一个最简单易懂的算法
EK算法
这是一个基于BFS实现的,使用DFS反复优化路径的算法。
听起来很难?但是实际非常轮椅简单
我们直接不解释,来实现一个这个程序的伪代码和代码模板:
e[i]存储第i条边的相关信息
其中v是e[i].to, c是e[i].capacity(容量),ne是e[i].nxt
总的来说是个链式前向星h[u]存储u的第一条出边,使我们开始链式前向星枚举边的起始地址,也就是head[]
mf[v]存的是S~v这条路上的流量上限,也就是max flow
pre[v]存的是前驱边,我们能从e[i]找到v,也需要从pre[v]找到这第i条边
BFS()找增广路(最短路思想)
- 1.初始化,mf[]=0,mf[S]=\(\infty\),S入队。
- 2.只要队不空,u=q.front()出队,广搜基本操作
- (1)枚举u的所有出边,更新u的最小容量,记录前驱边,扩展它的儿子入队
- (2)若能走到T,则返回true,确认这是一条可行的增广路
- (3)若走不到T,则返回false,我们求完最大流了
EK()求最大流(类似挤牙膏)
循环找增广路,每找到一条,
- (1)逆序更新残留网,去维护这个图让BFS可以跑下一次,容量“你死我活此消彼长”
- (2)累加可行流,最后返回最大值
struct edge {
LL v, c, ne;
}e[M];//存储E条边
int h[N], idx = 1;
//idx要从2、3开始配对,因为0是边的终点,不能用0、1
void add (int a, int b, int c) {
e[++idx] = { b, c, h[a]};//存入去往的点、容量、链式存储结构中的id
h[a] = idx;
}
add(a, b, c);add(b, a, 0);
//建立反向边的时候容量从0开始,和正向边互补一下
bool BFS() {
memset(mf, 0, sizeof(mf));//别忘记初始化mf
queue < int > q;
q.push(S);//源点入队
mf[S] = 1e9;//因为是超级源点的说,后面一遍一遍去min它
while(!q.empty()){//这一层如果还有下一层就继续搜下去
int u = q.front();
q.pop();//队头出队,搜下一层
for(int i = h[u]; i; i = e[i].ne){
LL v = e[i].v;
if(mf[v] == 0 && e[i].c ){//如果没有被搜到过而且是有容量的
md[v] = min(mf[u], e[i].c);//能不能塞下祖先的流量,如果不行就把整个流都变成自己的形状
pre[v] = i;//记录自己前一条边的编号为i
q.push(v);//入队继续BFS
if(v == T)return true;//终于找到终点了,我要好好地return true
}
}
}
return false;//不存在增广路,我们已经贪完了,直接回dfs输出sum(max_flow)每条增广路最大流量的总和,就是最大流
}
//我们在拥有反向边的情况下,更新完残留网后的每一遍BFS,都拥有通过反向边与增广路的相互优化比较来确保贪心的后效性被补上,让原来的增广路顺利的 飞 起 来 。
LL EK(){
LL flow = 0;//即ans
while(bfs()){//有增广路被找到就while
int v = T;//要从汇点逆着找上去,因为我们造了一个pre数组,已经通过BFS找到了这条增广路的路径了
while(v != S){//开始回溯
int i = pre[v]//找到回边
e[i].c -= mf[T];//更新每个点的剩余流量
e[i^1].c += mf[T]//i^1就是我们的反向边,因为和正向边互补,所以要加上mf,至于为什么是i^1,是因为我们存边的时候是两个两个存的,你要是愿意用i+1也行
v = e[i^1].v;//通过反向边继续换在路径上的上一个的下一个找,也就是继续找回去
}
flow += mf[T];//总量叠加上这一条的量
}
return flow;
}
程序时间复杂度\(O\)(\(V^2E\))
时间复杂度烂完了
Dinic算法——EK的更优解
很明显,我们总感觉这么多遍BFS有点浪费
所以有神牛就想出算法来优化了。
所以我们预先将这张图按照节点和源点的距离,对每个点分层,在选择路径的时候按照这个层号递增地走。
好熟悉啊,这是什么……哦……是topological排序!是拓扑。
我们预先构造分层图,在同一分层图上多次寻找增广路,虽然好像最严格的的时间复杂度上界也是\(O\)(\(V^2E\)),但是平常的时候还是\(O\)(\(VE\))的,甚至对于二分图可以\(O\)(\(\sqrt{V}E\))
在实际应用中,Dinic 算法通常表现得非常优秀,尤其是在随机图或一般实际问题中,由于网络结构的稀疏性和增广路径分布较为均匀,使得分层和增广过程远低于最坏情况所描述的复杂度。事实上,仅有一些特意构造的数据或特殊的网络结构才能使得 Dinic 算法达到理论上的最差性能。
——知乎
这边有过EK的算法逻辑铺垫,直接给出代码模板和注释也没什么问题:
(所谓 弧 便是分的层)
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e4 + 5;
const int MAXM = 2e5 + 5;
struct Edge {
int to, next, flow;
} edges[MAXM << 1];
int head[MAXN], cnt = 1; // 从1开始存边
int level[MAXN], cur[MAXN]; // 当前弧优化
int n, m, s, t; // 点数、边数、源点、汇点
inline void add_edge(int u, int v, int f) {
edges[++cnt] = {v, head[u], f};
head[u] = cnt;
edges[++cnt] = {u, head[v], 0}; // 反向边初始为0
head[v] = cnt;
}
bool bfs() {
memset(level, 0, sizeof(level));
queue<int> q;
q.push(s);
level[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = edges[i].next) {
int v = edges[i].to;
if (edges[i].flow && !level[v]) {
level[v] = level[u] + 1;
q.push(v);
}
}
}
return level[t];
}
int dfs(int u, int flow) {
if (u == t) return flow;
int res = 0;
for (int &i = cur[u]; i; i = edges[i].next) { // 当前弧优化
int v = edges[i].to;
if (edges[i].flow && level[v] == level[u] + 1) {
int f = dfs(v, min(flow, edges[i].flow));
edges[i].flow -= f;
edges[i ^ 1].flow += f;
res += f;
flow -= f;
if (!flow) break;
}
}
return res;
}
int dinic() {
int maxflow = 0;
while (bfs()) {
memcpy(cur, head, sizeof(head)); // 重置当前弧
maxflow += dfs(s, INF);
}
return maxflow;
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
for (int i = 1; i <= m; ++i) {
int u, v, f;
scanf("%d%d%d", &u, &v, &f);
add_edge(u, v, f);
}
printf("%d\n", dinic());
return 0;
}
日拱一卒,功不唐捐——Noivelist
网络流初步浅谈:EK与Dinic的更多相关文章
- 网络(最大)流初步+二分图初步 (浅谈EK,Dinic, Hungarian method:]
本文中 N为点数,M为边数: EK: (brute_force) : 每次bfs暴力找到一条增广路,更新流量,代码如下 : 时间复杂度:O(NM²): #include<bits/stdc++ ...
- 网络流$1$·简单的$EK$与$Dinic~of~Net-work ~ Flow$学习笔记
\(2333\)这是好久之前学的了,不过一直在咕咕咕咕. 一般来讲,正常的网络流笔记一开始都是要给网络流图下定义的.那么我们不妨也来先进行一波这种操作. 那么网络流图,类似于有向图,边上带权,但是这个 ...
- NIO原理剖析与Netty初步----浅谈高性能服务器开发(一)
除特别注明外,本站所有文章均为原创,转载请注明地址 在博主不长的工作经历中,NIO用的并不多,由于使用原生的Java NIO编程的复杂性,大多数时候我们会选择Netty,mina等开源框架,但理解NI ...
- 浅谈Hybrid技术的设计与实现第三弹——落地篇
前言 接上文:(阅读本文前,建议阅读前两篇文章先) 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 根据之前的介绍,大家对前端与Native的交互应该有一些简单的认识了,很多 ...
- 浅谈HTTPS以及Fiddler抓取HTTPS协议
最近想尝试基于Fiddler的录制功能做一些接口的获取和处理工作,碰到的一个问题就是简单连接Fiddler只能抓取HTTP协议,关键的登录请求等HTTPS协议都没有捕捉到,所以想让Fiddler能够同 ...
- 浅谈iOS视频开发
浅谈iOS视频开发 这段时间对视频开发进行了一些了解,在这里和大家分享一下我自己觉得学习步骤和资料,希望对那些对视频感兴趣的朋友有些帮助. 一.iOS系统自带播放器 要了解iOS视频开发,首先我们从 ...
- 关于AngularJS学习整理---浅谈$scope(作用域) 新手必备!
作为初次接触 AngularJS的新手,想要深层理解里面的内容短时间还是不可能的,所以标题写了浅谈字样,以下内容是参考各位大神以及相关书籍整理加个人理解,出现错误的地方请大家指正. $scope(作用 ...
- MVC模式浅谈
MVC模式浅谈 一.MVC模式概述 模型-视图-控制器(MVC模式)是一种非常经典的软件架构模式,在UI框架和UI设计思路中扮演着非常重要的角色.从设计模式的角度来看,MVC模式是 一种复合模式,它将 ...
- 浅谈HTTPS以及Fiddler抓取HTTPS协议(摘抄)
一.浅谈HTTPS 我们都知道HTTP并非是安全传输,在HTTPS基础上使用SSL协议进行加密构成的HTTPS协议是相对安全的.目前越来越多的企业选择使用HTTPS协议与用户进行通信,如百度.谷歌等. ...
- 【分析】浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang)
[分析]浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang) 今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间 ...
随机推荐
- opengl 学习之 14
opengl 学习 之 14 lesson 简介 生成一个纹理??其实看到这里有点看不懂了.哭 link http://www.opengl-tutorial.org/uncategorized/20 ...
- vscode linux c++ 配置
简介 最官方的配置方案 https://code.visualstudio.com/docs/cpp/config-linux 有三个文件会生成 tasks.json (编译器构建设置) launch ...
- cuda 如何安装 18.04 ubuntu
简介 先安装好Nvdia 驱动 在安装cuda 安装方式 https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_ ...
- .NET周刊【7月第1期 2025-07-06】
国内文章 .NET中全新的MongoDb ORM框架 - SqlSugar https://www.cnblogs.com/sunkaixuan/p/18959484 .NET中MongoDB ORM ...
- tf.keras.layers.Embedding + tf.keras.layers.TextVectorization + tf.keras.layers.StringLookup + tf.keras.layers.IntegerLookup
Turns positive integers(indexes) into dense vectors of fixed size. e.g. [[4], [20]] -> [[0.25, 0. ...
- ORACLE数据库表空间使用率查询-九五小庞
SELECT a.tablespace_name "表空间名", total "表空间大小", free "表空间剩余大小", (total ...
- Umi 约定式路由解析
可以少去理解一些不必要的概念,而多去思考为什么会有这样的东西,它解决了什么问题,或者它的运行机制是什么? 1. 什么是路由 路由是根据不同的 URl 地址展示不同的内容或页面.组件.一个针对 Reac ...
- leedcode 928. 尽量减少恶意软件的传播 II (并查集)
测试链接:https://leetcode.cn/problems/minimize-malware-spread-ii/ 思路 设置sz,inflect,virus[](该节点是否属于感染节点),f ...
- Linux下的.so文件是动态链接库
Linux下的.so是基于Linux下的动态链接,其功能和作用类似与windows下.dll文件. 通常情况下,对函数库的链接是放在编译时期(compile time)完成的.所有相关的对象文件(ob ...
- el-upload file转blob 用于预览pdf.js和下载文件
// file转blob fileToBlob(file, callback) { const type = file.type; const reader = new FileReader(); r ...