【网络流】——初识(最大流)

news2024/9/24 5:24:45

网络流-最大流

    • 基础信息
      • 引入
      • 一些概念
      • 基本性质
  • 最大流
      • 定义
    • Ford–Fulkerson 增广
    • Edmons−Karp算法
    • Dinic 算法
      • 参考文献

基础信息

引入

假定现在有一个无限放水的自来水厂和一个无限收水的小区,他们之间有多条水管和一些节点构成。

每一条水管有三个属性:流向,流量,容量。我们用 ( u , v ) (u,v) (u,v) 表示一条水管,这意味着水管中的水只能从 u u u 流向 v v v,而不能从 v v v 流向 u u u。流量即经过这条水管的单位时间内经过这条水管的水量。

我们将其模型化成为一个有向图,如下图所示,边上的数字即为水管的容量,流向用箭头来表示。当然,现在所有的水管流量都是 0 0 0

在这里插入图片描述

对于这一类型的有向图,我们称之为流网络。

一些概念

对于一个流网络,我们有如下几个概念:

  • 源点:发送流的节点。
  • 汇点:接收流的节点。
  • 弧:流网络图中的有向边,为了方便,后文均用“边或弧”表示
  • 弧的流量:在一个流网络中,每一条边都有一个流量,即单位时间内流经该边的流的量。一般地,我们使用流量函数 f ( x , y ) f(x,y) f(x,y) 表示 ( x , y ) (x,y) (x,y) 的流量。
  • 弧的容量:在一个流网络中,每一条边都会有一个容量限制,即边上流量的最大值。一般地,我们使用容量函数 c ( x , y ) c(x,y) c(x,y) 表示 ( x , y ) (x,y) (x,y) 的容量。
  • 弧的残量:即每一条边的剩余容量,可以表示为 c ( x , y ) − f ( x , y ) c(x,y)-f(x,y) c(x,y)f(x,y),用 c f ( u , v ) c_f(u,v) cf(u,v) 表示
  • 容量网络:已知每一条边的容量的流网络即为容量网络
  • 流量网络:已知每一条边的流量的流网络即为流量网络
  • 残量网络:已知每一条边的残量的流网络即为残量网络。所有边的流量均为 0 0 0 的残量网络就是容量网络。用 G f G_f Gf 表示,即 G f = ( V , E f ) , E f = G_f=(V,E_f),E_f= Gf=(V,Ef),Ef={ ( u , v ) ∣ c f ( u , v ) > 0 (u,v)|c_f(u,v)>0 (u,v)cf(u,v)>0 }

请确保你对概念比较熟悉

基本性质

  1. 容量限制: ∀ ( x , y ) ∈ E , 0 ≤ f ( x , y ) ≤ c ( x , y ) \forall (x,y)\in E,0\le f(x,y)\le c(x,y) (x,y)E,0f(x,y)c(x,y)
  2. 斜对称性: ∀ ( x , y ) ∈ E , f ( x , y ) = − f ( y , x ) \forall (x,y)\in E,f(x,y)=-f(y,x) (x,y)E,f(x,y)=f(y,x)
  3. 流量守恒:除了源点与汇点之外,流入任何节点的流一定等于流出该节点的流。

最大流

定义

在这里插入图片描述
通俗地讲,回到引例,现在有一个问题需要我们去解决:水厂在单位时间内最多能发送多少水给小区?
这就是网络流中的一个问题:最大流问题。
在这里插入图片描述

Ford–Fulkerson 增广

  • 假设有源点到汇点的一条可行路径 R R R,满足 ∀ ( x , y ) ∈ R , c f ( x , y ) > 0 \forall(x,y)∈R,c_f(x,y)>0 (x,y)R,cf(x,y)>0,即残量为严格大于 0 0 0,我们称 R R R 为一条增广路。
  • 此时我们可以得出一个简单的思路:在残量网络中不断地寻找增广路,从源点向汇点发送流。该增广路的流量满足 0 < f ≤ m i n ( c f ( x , y ) ) 0<f\le min(c_f(x,y)) 0<fmin(cf(x,y)),为了取得最大流,我们自然而然的令该增广路的流量为 min ⁡ ( c f ( x , y ) ) \min(c_f(x,y)) min(cf(x,y)),然后修改路径上每一条边的残量即可。
  • 这个思路即为Ford−Fulkerson方法,简称为FF方法。
  • 可以使用DFS实现基本的Ford−Fulkerson算法。
  • 为了保证算法的正确性,有时候我们需要缩减流网络中一些特定边的流量。
  • 举个例子,如图。

假定我们使用DFS找到了红色的这一条增广路径,显然此时源点到汇点的流量为1。此时图中不再有任何增广路径,但是这个流是最大流吗?
在这里插入图片描述
显然不是,我们可以找到更好的,如图:

在这里插入图片描述
此时流量为 2 2 2,这才是最大流。

  • 问题出在哪里?
  • 由于我们没有给程序一个反悔的机会,所以才会出现上面这样的尴尬情况。
  • 那么如何解决这个问题呢?
  • 引入“后向弧”。我们给每一条边 ( u , v ) (u,v) (u,v) 建立一条对应的反向边 ( v , u ) (v,u) (v,u),用于对正向边流量的缩减。
  • 很自然地,我们会把反向边的初始残量设置为 0 0 0,因为没有正向流量,无法缩减。
  • 那么观察下面的算法图示:

在这里插入图片描述
然后对于初学者可能会注意到:反向边的流量 f ( v , u ) f(v,u) f(v,u) 可能是一个负的,这里可以参考一下 OI-WIKI 的解释。

在这里插入图片描述
在这里插入图片描述

是不是有点懵?

  • 通俗的文字解释就是:反向边的功能是将正向边的流量往回推送,此时反向边推送的流量(反向流量)最多恰好把正向流量抵消,所以反向边的残量等于正向边流量。
  • 综上所述,反向边的残量应当是动态更新,一旦正向边的流量更新,反向边的残量也需要更新。

Edmons−Karp算法

观察到基于 DFS 的FF 可能不是很优。

  • 观察这样一张图,如果我们使用基于DFS实现的FF方法,假定一开始找到的增广路径为红色的这一条,那么我们可能需要反复进行 999 × 2 999\times 2 999×2次DFS才能够找到最大流。
    在这里插入图片描述
  • 但是事实上,我们在最好情况下只需要走两次(直接走 999 999 999 的边)就能够达到最大流。
  • 在这种情况下,我们引入EK算法。其基础仍然是FF方法,但是我们不再使用DFS,而是转为使用BFS寻找最短增广路改进效率,时间复杂度为 O ( n m 2 ) O(nm^2) O(nm2)

参考代码:

queue<int> que;flow[s]=0x3f3f3f3f;que.push(s);
for (int i=1;i<=n;i++)prep[i]=-1,pree[i]=0;
prep[s]=0;
while(!que.empty())
{
	int now=que.front();
	que.pop();
	for (int i=head[now];i;i=e[i].next)
	{
		if(e[i].val>0&&prep[e[i].to]==-1)
		{
			flow[e[i].to]=min(flow[now],e[i].val);//flow记录的是在增广路上经过该点的流量
			pree[e[i].to]=i;//用于记录前驱边的编号
			prep[e[i].to]=now;//用于记录前驱节点
			if (e[i].to==t) break;
			que.push(e[i].to);
		}
	}
}
if (prep[t]!=-1) return flow[t];
else return -1
  • 下一步就是对路径上的所有边进行信息的更新。
  • 现在有一个问题,我们如何快速取得反向边呢?
  • 对于链式前向星,我们设置第一条边的编号为 2 2 2 ,我们存入一条正向边时,下一条边就存入反向边,那么只要对一条边的编号异或 1 1 1 就能取得它对应的反向边。
  • 证明:偶数的二进制表示最后一位为 0 0 0 ,对这个偶数异或 1 1 1 相当于对这个偶数 + 1 +1 +1。奇数的二进制表示最后一位为 1 1 1,对这个奇数异或 1 1 1 相当于对这个奇数 − 1 -1 1
    那么路径的信息更新就可以轻松实现了。
    在这里插入图片描述

Dinic 算法

  • 由于EK算法每次只求一条最短增广路,其效率在某些情况下可能不够优秀。
  • 对于下面这一张图,如果我们使用EK算法,那么我们至少需要重复三次EK算法的流程才能求出最大流。

在这里插入图片描述

  • 自然而然地,我们会想到能不能实现多路增广呢?

于是 Dinic 算法就出来了。(其实就是把EK和FF融在一起)

Dinic算法的流程如下:

  1. BFS对流网络分层。
  2. DFS对图上增广路的信息进行更新。
    在这里插入图片描述

如图所示,此时已经完成了对于流网络的分层,点上的编号即为所在的层数。
这个时候我们从源点开始DFS,在最好情况下,我们能同时找到三条增广路,即标红色的三条。

  • BFS对图分层的作用在于一次可以得到多条长度相同的最短增广路。
  • 那么路径的信息应该如何更新呢?
  • 每次从当前点出发,选用从当前点所在层到下一层的边,发送一定的流量,流量的大小取边残量和当前点从源点获取的剩余流中两者的最小值。
  • 搜索完成后,即不再有流能够往后发送,或者能够抵达汇点。此时返回一个流量值,即这条增广路的流量(若不再有流能够往后发送,则返回的流量值为0),此时就能够对边和反向边的残量进行更新了。
  • Dinic算法就完成了,其时间复杂度为 O ( n 2 m ) O(n^2 m) O(n2m)
  • 显然,这样的时间复杂度并算不上多么高效,原因在于尽管我们一次BFS找到了多条增广路,但是DFS时路径的信息仍然是一条一条更新的。
    参考代码:
    BFS实现:
    在这里插入图片描述

实现难度不大,只是一个模板BFS。
dis数组用于记录层数,vis数组用于记录是否被访问过。
事实上vis数组是不必要的,因为dis数组也可以实现一样的功能。

DFS实现:
在这里插入图片描述

注意到,Dinic算法的复杂度上界也不是很优, 所以,我们会考虑对DFS的过程加入一定的优化。

当前弧优化

  • 在DFS的过程中,我们可能会多次经过一个点。我们会重复的处理一些边。
  • 但是事实上,在每次处理的过程中,已经处理完毕的边在这次DFS中不再有任何作用,一旦处理完毕,该边的“潜力”一定已经被榨干了。
  • 所以,我们每次只需要记录当前处理的边的编号,下次经过这个点的时候,可以直接从这条边开始。
  • 这就叫作当前弧优化。

证明:增广次数为 O ( m ) O(m) O(m),每次增广最多经过 O ( n ) O(n) O(n) 个点,总复杂度为 O ( n m ) O(nm) O(nm)

注意,不写这个优化,复杂度是错的,可能退化为 O ( n m 2 ) O(nm^2) O(nm2)

点优化:

  • 假如从一个点流不出流量,则把该点的dis变为 − 1 -1 1,这样这一次多路增广再也不会来了。

  • 大多数情况下这只能优化常数,但是在某些毒瘤题里面跑的很快。

这就是常用的两个优化,更多的可以参考 command_block大佬的博客。

虽然EK和Dinic的时间复杂度上界都不是非常优秀,但是在实际应用上效率非常高。
对于EK算法,一般能够解决 1 0 3 到 1 0 4 10^3 \text{到}10^4 103104 的网络流问题。
对于Dinic算法,一般能够解决 1 0 4 到 1 0 5 10^4 \text{到}10^5 104105 的网络流问题。

Dinic完整的参考代码:

#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL)
using namespace std;
const int N=1e5+1,inf=1e9;
struct fy{
	int v,w,nxt;
}e[N];
int head[N],idx=1,n,m,s,t,ans=0,dis[N],cur[N],vis[N];
void add(int x,int y,int z){
	e[++idx].v=y,e[idx].w=z,e[idx].nxt=head[x],head[x]=idx;
}
bool bfs(){
	for(int i=1;i<=n;i++)
		dis[i]=0,vis[i]=0,cur[i]=head[i];
	vis[s]=1,dis[s]=1;
	queue<int>Q;
	Q.push(s);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(!vis[v]&&e[i].w>0){
				dis[v]=dis[u]+1;
				vis[v]=1;
				if(v==t)
					return 1;
				Q.push(v);
			}
		}
	}
	return 0;
	
}
int dfs(int u,int flow){
	if(!flow||u==t)
		return flow;
	int used=0;
	for(int i=cur[u];i;i=e[i].nxt){
		cur[u]=i;
		int v=e[i].v;
		if(dis[u]+1!=dis[v])
			continue;
		int _=dfs(v,min(flow-used,e[i].w));
		if(_){
			e[i].w-=_;
			e[i^1].w+=_;
			used+=_;
			if(flow-used==0)
				return flow;
		}
	}
	return used;
}
signed main(){
	IOS;
	cin>>n>>m>>s>>t;
	for(int i=1,x,y,z;i<=m;i++)
		cin>>x>>y>>z,add(x,y,z),add(y,x,0);
	while(bfs())
		ans+=dfs(s,inf);
	cout<<ans<<"\n";
	return 0;
}

当然,常用的是Dinic,但还有MPN算法,ISAP,Push-Relabel 预流推进算法 等其他方法,可能以后会填坑

参考文献

  1. OI-WIKI
  2. command_block的博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1950243.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

sql_exporter通过sql收集业务数据并通过prometheus+grafana展示

下载并解压安装sql_exporter wget https://github.com/free/sql_exporter/releases/download/0.5/sql_exporter-0.5.linux-amd64.tar.gz #解压 tar xvf sql_exporter-0.5.linux-amd64.tar.gz -C /usr/local/修改主配置文件 cd /usr/local/ mv sql_exporter-0.5.linux-amd64 s…

海山数据库(He3DB)技术解析:海山Redis定时任务与持久化管控设计

文章目录 引言一、背景介绍二、具体实现1、多副本容灾功能2、主备切换后任务断点续做功能3、持久化管控编排功能 三、总结作者 引言 云Redis数据库服务是目前广泛应用的模式&#xff0c;其数据持久化方案是现在研究的热点内容&#xff0c;数据持久化操作主要由参数设置自动触发…

AI学习记录 - 激活函数的作用

试验&#xff0c;通过在线性公式加入激活函数&#xff0c;可以拟合复杂的情况&#xff08;使用js实现&#xff09; 结论:1、线性函数的叠加&#xff0c;无论叠加多少次&#xff0c;都是线性的 如下图 示例代码 线性代码&#xff0c;使用ykxb的方式&#xff0c;叠加10个函数…

AnimationCurve动画曲线 简单使用

资料 AnimationCurve AnimationCurve 表示一条曲线。可在曲线上添加关键帧&#xff0c;编辑曲线。 水平轴表示时间&#xff0c;竖直轴表示曲线的高度 获取曲线高度方法&#xff0c;AnimationCurve.Evaluate 示例 循环移动Cube,Cube沿着曲线移动 using UnityEngine; publ…

正则表达式与文本处理

目录 一、正则表达式 1、正则表达式定义 1.1正则表达式的概念及作用 1.2、正则表达式的工具 1.3、正则表达式的组成 2、基础正则表达式 3、扩展正则表达式 4、元字符操作 4.1、查找特定字符 4.2、利用中括号“[]”来查找集合字符 4.3、查找行首“^”与行尾字符“$”…

火山引擎边缘智能平台,让AI走进企业现场

如何让大模型更好地与生产进行融合&#xff0c;让AI生产力为企业降本增效&#xff0c;是每个企业都在关注的问题。但设备异构、隐私安全、传输延迟等困难&#xff0c;让大模型走进企业现场变得步履维艰。这种情况&#xff0c;就需要借助边缘智能来应对这些挑战。 什么是边缘智能…

二维数组前缀和

二维数组前缀和&#xff08;Leetcode304&#xff09; 想法(参考题解)&#xff1a; 如上图&#xff0c;在矩阵中根据给定的方框围成的范围&#xff0c;确定范围内元素之和。题目&#xff1a;二维区域和检索 - 矩阵不可变。思路就是使用前缀和&#xff0c;前缀和表示的是面积&am…

Linux 安装 GDB (无Root 权限)

引入 在Linux系统中&#xff0c;如果你需要在集群或者远程操作没有root权限的机子&#xff0c;安装GDB&#xff08;GNU调试器&#xff09;可能会有些限制&#xff0c;因为通常安装新软件或更新系统文件需要管理员权限。下面我们介绍可以在没有root权限的情况下安装GDB&#xf…

微信小程序获取蓝牙并实现内容打印

通过微信小程序如何实现获取蓝牙打印机并实现打印能力&#xff0c;之前做过一个测试Dome&#xff0c;能够获取附近的蓝牙打印机设备并实现打印,今天开放出来供大家参考。 wxml <!--右下角搜索--> <view class"ly-cass-box"><view class"ly-cas…

【Python第三方库】PyQt5安装与应用

文章目录 引言安装PYQT5基于Pyqt5的简单桌面应用常用的方法与属性QtDesigner工具使用与集成窗口类型QWidget和QMainWindow区别 UI文件加载方式直接加载UI文件的方式显示窗口转化py文件进行显示窗口 PyQt5中常用的操作信号与槽的设置绑定页面跳转 引言 PyQt5是一个流行的Python…

Java——多线程(2/9):线程创建方式三Callable,Thread的常用方法(如何创建、代码实例、API及优缺点)

目录 方式三&#xff1a;实现Callable接口 前言 如何创建 代码实例 API及优缺点 Thread的常用方法 代码演示 方式三&#xff1a;实现Callable接口 前言 前两种线程创建方式都存在的一个问题 假如线程执行完毕后有一些数据需要返回&#xff0c;他们重写的run方法均不能…

算法与算法分析

目录 一.前言 二.算法的特性和要求 三.分析算法--时间效率 四. 分析算法--空间效率 一.前言 算法就是对特定问题求解方法和步骤的一种描述&#xff0c;它是指令的有限序列。其中&#xff0c;每个指令表示一个或多个操作。总而言之&#xff0c;我们数据结构就是通过算法实现操…

如何根据员工的反馈来确定六西格玛培训需求?

在六西格玛的推广与实施过程中&#xff0c;最大的挑战往往不在于技术本身&#xff0c;而在于如何让每一位员工都能理解、接受并积极参与其中。员工是企业最直接的执行者&#xff0c;他们的声音直接反映了项目落地的难易程度及潜在障碍。因此&#xff0c;倾听并有效整合员工反馈…

Python Django功能强大的扩展库之channels使用详解

概要 随着实时 web 应用程序的兴起,传统的同步 web 框架已经无法满足高并发和实时通信的需求。Django Channels 是 Django 的一个扩展,旨在将 Django 从一个同步 HTTP 框架转变为一个支持 WebSockets、HTTP2 和其他协议的异步框架。它不仅能够处理传统的 HTTP 请求,还可以处…

STM32串口(串口基础)

串口整个东西可以说但凡你要碰单片机&#xff0c;想做点上点档次的东西的话那你就包用它的。32的串口配置并不难&#xff0c;哪怕是比起51其实也难不到哪去。 目录 一.通信基础 1.通信方式 2.通信速率 二.串口基础 1.串口的数据帧结构&#xff08;协议&#xff09; 2.ST…

【轨物方案】开关柜在线监测物联网解决方案

随着物联网技术的发展&#xff0c;电力设备状态监测技术也得到了迅速发展。传统的电力成套开关柜设备状态监测方法主要采用人工巡检和定期维护的方式&#xff0c;这种方法不仅效率低下&#xff0c;而且难以保证设备的实时性和安全性。因此&#xff0c;基于物联网技术的成套开关…

JDBC标准实现

JDBC是什么 Java Database Connectivity&#xff1a;Java访问数据库的解 决方案 JDBC定义了一套标准接口&#xff0c;即访问数据库的通用API&#xff0c; 不同的数据库厂商根据各自数据库的特点去实现这些接口。 JDBC希望用相同的方式访问不同的数据库&#xff0c;让具体 的…

Nacos 2.x 新增 grpc 端口,Nginx 需要配置TCP端口转发的注意事项

Nacos 2.x 开始&#xff0c;最大的变化就是端口。在默认主端口 8848 之外又新增了三个端口&#xff0c;新增端口是在配置的主端口 server.port 的基础上&#xff0c;进行一定偏移量自动生成。 8848&#xff08;主端口&#xff0c;默认8848&#xff09;web页面端口及相关http接口…

医院等保解决方案有哪些?用哪些软件可以加强等级保护?

在医疗领域&#xff0c;信息系统的安全直接关系到患者的隐私保护、医院的正常运营乃至社会的稳定。医院信息系统面临着越来越多的安全挑战。 为了确保患者信息的安全与隐私保护&#xff0c;医院需要采取有效的等保&#xff08;等级保护&#xff09;措施。那么&#xff0c;医院…

数据恢复教程:如何从硬盘、SD存储卡、数码相机中恢复误删除数据。

您正在摆弄 Android 设备。突然&#xff0c;您意外删除了一张或多张图片。不用担心&#xff0c;您总能找到一款价格实惠的数据恢复应用。这款先进的软件可帮助 Android 用户从硬盘、安全数字 (SD) 或存储卡以及数码相机中恢复已删除的数据。 Android 上数据被删除的主要原因 在…