论网络流(最大流篇)--新手入门超详解--包教包会
论网络流--新手入门超详解--包教包会
- 1 前言
- 2 什么是最大流
- 3最大流问题的求解
- (1)问题转化--增广路的引入
- (2)走回头路--EK算法
- (3)EK的弊端
- (4)化图为树--DINIC算法
- 4后记
1 前言
网络流是图论算法,阅读本文需要以下前置知识
1搜索算法(dfs+bfs)
2图的存储
3最短路径算法(dijkstra,floyd,spfa)
所有数学公式均使用Latex并附有证明
作为网络流系列的第一张,我们从网络流的基础–最大流开始
2 什么是最大流
我们把网络流这个词拆开来看
网络,指的就是图,在网络流问题中大多为有向图,而且有边权(容量)
流,顾名思义,就是向水一样,从一个水源(源点)流出,流向终点(汇点)
那么会有多少水流向终点呢?这就是流量
这么说肯定不好理解,我们从图的最简单形态–链出发
上图片

可以把源点看做自来水厂,汇点看做你家,中间的点就是中转站,边就是水管,水管当然不是无限大的,肯定会有容量(即边权,图上用蓝色数字标出)
假设你需要 1 1 1个单位的水,自来水厂给你供水,每个水管中流的水量在下图中用绿色标注

显然,每个水管中的水量都小于容量,水管指定炸不了
流量为 1 1 1,即你最后收到了 1 1 1个单位的水
流量可以达到 1 1 1,没有任何一个水管炸掉,这就是可行流
但是这个没啥用…流量为 0 0 0还是可行流呢
我们考虑对于一个所有可行流的集合,求其中流量最大的一个
对于这张图,答案显然了,流量为2,在下图中用橙色标出
可以看到容量为 2 2 2的水管装满水了,不能再多了,这就是最大流
3最大流问题的求解
(1)问题转化–增广路的引入
假设图是一条链,答案肯定为容量最小边的容量,要不然就炸了
但是如果图不是一条链呢
我们把图变成链不就好了
在图上取一条路径,不就形成了链吗
我们还是举上面的例子,自来水厂给你家供水,肯定是选取一条路径将水送到你家,但是如果不够用,自来水厂就会再选取一条路径给你家供水
这就是增广路–一条连接源点和汇点,且流量未满的路径
假设现在已经找到了一条增广路,我们该怎么更新
根据上面的例子,自来水厂把水送到你家,肯定占用了水管,有的水管占满了,但是有的水管还有空间
我们求出增广路的流量–根据刚才链上问题的结论–就是这些边中容量的最小值
我们将路径上每一条边都减去这个占用的容量即可
这就构成了人们常说的残余网络
都减去容量了,我们只要用bfs求出原点到汇点的任意路径即可
如果不连通,算法就结束
这个算法好不好使,手动模拟就知道,请看图片

bfs求出一条增广路为 1 − > 2 − > 3 − > 4 1->2->3->4 1−>2−>3−>4
减去流量得下图

找不出增广路了,答案为 1 1 1
但是显然答案就不是 1 1 1,选取 1 − > 2 − > 4 1->2->4 1−>2−>4, 1 − > 3 − > 4 1->3->4 1−>3−>4两条增广路,答案显然为 2 2 2,我们的算法就这样炸了
(2)走回头路–EK算法
我们发现,无论怎么选取增广路,我们很难找出一个贪心策略一次得到最优解,选取不同增广路得到的结果不同
如果我们能走回头路多好
但是减去容量是算法成立的基础,我们不能动这一步
所以,我们要引入最大流的精髓–反向边
还是那个例子,自来水厂给你家送水,已经找到一条路径
但是,你说水不够,再给我送点,然而此时水管被占用了
那咋办,你直接把水送回去呗
所以,减去的容量,我们建反向边,把水送回去,就是走回头路
回归问题,我们求出 1 − > 2 − > 3 − > 4 1->2->3->4 1−>2−>3−>4的增广路中,图变成了这样
(反向边用红色标出)

这下,我们可以再次找到一条增广路 1 − > 3 − > 2 − > 4 1->3->2->4 1−>3−>2−>4
得到结果 2 2 2,AC了,那么,这个算法为什么是对的
其中涉及到了边 2 − > 3 2->3 2−>3,但是如果我们手动模拟,就会发现根本不能取这条边
我们发现 3 − > 2 3->2 3−>2这条反向边本来就代表着走回头路
但是正常 3 − > 4 3->4 3−>4这条边已经走过了,既然你替我走了,我就替你走,直接去走路径 2 − > 4 2->4 2−>4,这就相当于交换了位置,结果不改变
这就是EK算法,最大流问题的基础算法
附代码(c++)(问题参考洛谷P2740)
#include<bits/stdc++.h>
using namespace std;
int a[1145][1145],dist[114514],n,m,start,endd;
queue<int>q;
bool vis[114514];
bool bfs(int s,int t){memset(vis,false,sizeof(vis));memset(dist,-1,sizeof(dist));vis[s] = true;dist[s] = s;while(!q.empty()){q.pop();}q.push(s);while(!q.empty()){int x = q.front();q.pop();for(int i = 1;i<=n;i++){if(i!=x&&!vis[i]&&a[x][i]>0){q.push(i); dist[i] = x;vis[i] = true;if(i==t){return true; }}}}return false;
}
int EK(int s,int t){int ans = 0;while(bfs(s,t)){int mins = 0x3f3f3f3f;for(int i = t;i!=s;i = dist[i]){mins = min(mins,a[dist[i]][i]);}for(int i = t;i!=s;i = dist[i]){a[i][dist[i]]+=mins;a[dist[i]][i]-=mins;}ans+=mins;}return ans;
}
int main() {cin>>n>>m>>start>>endd;for(int i = 1;i<=m;i++){int u,v,w;cin>>u>>v>>w; a[u][v]+=w;}int ans = EK(start,endd);cout<<ans;return 0;
}
(3)EK的弊端
EK算法是正确的,但是时间复杂度非常玄学
比如说这种图

如果使用EK算法, 2 − > 3 2->3 2−>3那条边的方向会不断切换,这样下去将会找 200 200 200次增广路
直接TLE
EK算法可以优化,每次处理出最短路径,但是这也没有解决EK慢的问题,尤其是在更加稠密的图,EK的复杂度显然不够优秀
所以,为了解决这种问题,DINIC诞生了
(4)化图为树–DINIC算法
EK一次只能找一条增广路
其实就是把图变成链
如果我们想要从源点出发一次找多条增广路呢?
我们把图变成树,怎么变?
显然,用bfs分层,形成的就是一棵树了
然而进行一次bfs不一定正确
那进行多少次bfs呢?这个就很玄学
每次的残余网络的分层都不相同,所以,可以通过一直进行bfs知道搜索不出增广路
有了搜索树,这就可以引入dfs了,一次搜更多的增广路
不仅提高了稠密图中的时间复杂度,还防止了反复更新增广路的问题
这就是DINIC,在bfs建出的搜索树上跑dfs
代码中的体现就是储存每个点的层次
DINIC还有一个著名的当前弧优化
因为搜索树上有重复点,这些重复点连的边已经更新了的话,就不会再更新,所以我们标记一个 c u r cur cur数组即可
其实,在DINIC之外还有ISAP等算法
但是在信奥中有个不成文的规定,不能卡DINIC
新手可以先掌握DINIC
附代码(c++)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
ll nxt[114514],to[114514],w[114514];
ll idx = 1,head[114514];
ll n,m,s,t;
ll dep[114514],cur[114514];
ll ans = 0;
queue<ll> q;
void addedge(ll u,ll v,ll ww){idx++;nxt[idx] = head[u];to[idx] = v;w[idx] = ww;head[u] = idx;
}
bool bfs(){for(int i = 1;i<=n;i++){dep[i] = INF;}while(!q.empty()){q.pop();}q.push(s);dep[s] = 0;cur[s] = head[s];while(!q.empty()){ll x = q.front();q.pop();for(ll i = head[x];i!=0;i = nxt[i]){ll y = to[i];if(w[i]>0&&dep[y]==INF){q.push(y);cur[y] = head[y];dep[y] = dep[x]+1;if(y==t){return true;}}}}return false;
}
ll dfs(ll x,ll res){if(x==t){return res;}ll sum = 0,k,i;for(i = cur[x];i&&res;i = nxt[i]){cur[x] = i;ll y = to[i];if(w[i]>0&&(dep[y]==dep[x]+1)){k = dfs(y,min(res,w[i]));if(!k){dep[y] = INF;}w[i]-=k;w[i^1]+=k;sum+=k;res-=k;}}return sum;
}
void DINIC(){while(bfs()){ans+=dfs(s,INF);}
}
signed main(){cin>>n>>m>>s>>t;for(ll i = 1;i<=m;i++){ll u,v,ww;cin>>u>>v>>ww;addedge(u,v,ww);addedge(v,u,0);}DINIC();cout<<ans;return 0;
}
4后记
本蒟蒻的第一篇博客讲的是最短路,如今20篇过去了,这是第21篇
我这个大蒟蒻终于学会了网络流
本文作者是蒟蒻,如有错误请各位神犇指点
森林古猿出品,必属精品,请认准CSDN森林古猿1
相关文章:
论网络流(最大流篇)--新手入门超详解--包教包会
论网络流--新手入门超详解--包教包会 1 前言2 什么是最大流3最大流问题的求解(1)问题转化--增广路的引入(2)走回头路--EK算法(3)EK的弊端(4)化图为树--DINIC算法 4后记 1 前言 网络…...
环境搭建:全面详尽的 MongoDB Shell MongoDB Server介绍、安装、验证与配置指南(以 Windows 系统为主)
环境搭建:全面详尽的 MongoDB Shell & MongoDB Server介绍、安装、验证与配置指南(以 Windows 系统为主) MongoDB 是一个基于文档的 NoSQL 数据库,以其高性能、灵活性和可扩展性而受到广泛欢迎。本文将带您完成 MongoDB 的安装…...
使用 OpenSearch 的 K-NN 向量搜索来增强搜索功能
使用 OpenSearch 的 K-NN 向量搜索来增强搜索功能 许多应用程序都依赖于提供精确且相关的搜索结果的能力。尽管传统关系数据库的全文搜索功能在某些情况下已经足够,但这些数据库在从文本中提取语义含义或搜索结构化程度较低的数据方面可能会出现不足。在这篇博文中&…...
Less-2(闭合)
我们使用第一关的测试方法尝试一下,打咩 直接看源码,看到,尝试一下闭合 <?php ini_set("display_errors", 0); $str $_GET["keyword"]; echo "<h2 aligncenter>没有找到和".htmlspecialchars($str)."相…...
mysql介绍
MySQL是一种开源的关系型数据库管理系统(RDBMS),广泛用于存储和管理数据。它支持多种操作系统,如Linux、Windows、MacOS等。MySQL的特点包括: 1.开源免费:MySQL是开源的,可以免费使用和分发。 2…...
【ROS学习】ROS中 use_sim_time 参数的含义与作用
文章目录 写在前面一、背景描述二、 use_sim_time 参数的含义与作用三、举例说明1. 不设置use_sim_time (也即 use_sim_time false),播放数据集使用rosbag play **.bag 2. 不设置use_sim_time (也即 use_sim_time false),播放数据集使用rosbag play **…...
python-查找元素3(赛氪OJ)
[题目描述] 有n个不同的数,从小到大排成一列。现在告诉你其中的一个数x,x不一定是原先数列中的数。你需要输出最后一个<x的数在此数组中的下标。输入: 输入共两行第一行为两个整数n、x。第二行为n个整数,代表a[i]。输出&#x…...
苹果 Safari 的隐私保护与广告追踪问题 :技术进展与挑战
隐私保护的进展与挑战 近年来,浏览器行业在隐私保护技术方面取得了显著进展,尤其是在广告追踪领域。谷歌的 Chrome 浏览器推广了隐私沙盒,通过将用户可能感兴趣的主题分类并推送给广告商。Mozilla Firefox 和 Meta Facebook 则推出了一种名为…...
pytest之fixture
Pytest 中 Fixture 的 yield 用法 在软件测试中,设置和清理测试环境是一个重要的环节。Pytest 作为一个功能强大的测试框架,通过 Fixture 机制简化了这一过程。特别是yield语句的使用,使得 Fixture 能够在测试前进行设置,并在测试…...
Rancher
文章目录 Rancher1. 安装和配置2. 服务部署和管理3. 容器自动化缩容和扩容 Rancher Rancher 是一个开源的企业级容器管理平台,旨在简化容器化应用的部署、管理和运维。它支持多种容器编排引擎,如 Kubernetes、Docker Swarm 等,并提供了统一的…...
Wordpress建站问题记录
从一月到七月因为工作的情况没有进行太深入的开发,想着整理一下把做一个独立站把博客多个渠道发布一下,遇到几个问题在这里记录一下. 先写一下我的配置 系统: centos7 php: 7.4 wordpress: 6.6.1 mysql:8.0.6 1. HTTP 500 Internal 这个问题出现在我将wordpress的文件夹全部…...
JavaFx中通过线程池运行或者停止多个周期性任务
在JavaFX中,要实现点击按钮启动多个周期性任务并通过多线程执行,并在任务结束后将结果写入多个文本组件中,同时提供另一个按钮来停止这些任务,你可以使用ScheduledExecutorService来管理周期性任务,并使用AtomicBoolea…...
使用RabbitMQ实现异步支付状态通知
在支付系统中,如何确保支付状态的准确传递和处理显得尤为重要。今天,我们将以一个支付流程为例,探讨在引入RabbitMQ前后的实现和优化。 改造前 在引入RabbitMQ之前,我们通常会直接在支付方法中完成所有的操作。这包括查询支付单…...
[最短路dijkstra],启动!!!
总时间复杂度为 O ( ( n m ) log m ) P4779 【模板】单源最短路径(标准版) #include<bits/stdc.h> #define ll long long #define fi first #define se second #define pb push_back #define PII pair<int,int > #define I…...
Java企业微信服务商代开发获取AccessToken示例
这里主要针对的是企业微信服务商代开发模式 文档地址 可以看到里面大致有三种token,一个是服务商的token,一个是企业授权token,还有一个是应用的token 这里面主要有下面几个参数 首先是服务商的 corpid 和 provider_secret ,这个可…...
How does age change how you learn?(2)年龄如何影响学习能力?(二)
Do different people experience decline differently? 不同人经历的认知衰退会有不同吗? Do all people experience cognitive decline uniformly?Or do some people’s minds slip while others stay sharp much longer? 所有人经历的认知衰退都是一样的吗?还是有些人…...
可验证随机函数 vrf 概述
一、什么是VRF 背景: 在传统的区块链中,常用的随机算法是基于伪随机数生成器(Pseudorandom Number Generator,PRNG)的。PRNG是一种确定性算法,它根据一个初始种子生成一个看似随机的序列。在区块链中,通常使用的是伪随机数序列来选择区块的创建者、确定验证节点的轮换…...
鸿蒙双向绑定组件:TextArea、TextInput、Search、Checkbox,文本输入组件,图案解锁组件PatternLock
对象暂不支持双向绑定, 效果: 代码: Entry Component struct MvvmCase {StateisSelect: boolean falseStatesearchText: String ""StateinputText: string ""StateareaText: string ""build() {Grid() {G…...
JS 算法 - 计数器
theme: smartblue 题目描述 给定一个整型参数 n,请你编写并返回一个 counter 函数。这个 counter 函数最初返回 n,每次调用它时会返回前一个值加 1 的值 ( n , n 1 , n 2 ,等等)。 示例 1: 输入: n 10 ["cal…...
JavaScript基础——JavaScript运算符
赋值运算符 算术运算符 一元运算符 三元/三目运算符 比较运算符 逻辑运算符 运算符优先级 在JavaScript中,常见的运算符可以包括赋值运算符、一元运算符、算术运算符(二元运算符)、三元/三目运算符、比较运算符、逻辑运算符等࿰…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...
