posted on 2022-08-12 14:14:05 | under 模板 | source

感谢讲师 LQS 带来的网络流专题。

本文非常不严谨,请不要把它当作入门博客。

0xFF 目录

  • 0x00 网络流及求法
    • 0x01 暴力最大流:Ford-Fulkerson 算法
    • 0x02 最大流:Edmord-Karp 算法
    • 0x03 最大流:Dinic 算法 template
    • 0x04 最小费用最大流:EK/Dinic + SPFA = SSP template
    • 0x05 最小费用最大流:Dinic + Johnson 最短路 = 原始对偶
  • 0x10 网络流基本套路
    • 0x11 常见 trick
    • 0x12 最小割及应用

0x00 网络流及求法

网络是一张边带权的有向图,这是一个例子:

其中有一个源点 \(S\),一个汇点 \(T\)。流量从 \(S\) 源源不断地流出,每条边可以承载边权这么多的流量,每个点(除了 \(S,T\))要把它接受的流量全部流出(不能剩余),最后汇集到 \(T\),就像一个排水系统在工作。

我们将最后到达 \(T\)最大流量称为这个网络的最大流(Maxinum Flow)。

如果边上还有一个费用,每有一个流量流过都要支付这么多费用,最后使 \(T\) 接受最大流量的前提下,所使用的最小费用,称作这个网络的最小费用最大流(Mininum Cost & Maxinum Flow)。

0x01 暴力最大流:Ford-Fulkerson 算法

我们有一个 naive 的想法:每次从 \(S\) 发出一个流量,让它一直流到 \(T\),直到它不能流为止。在流的过程中,每流过一条边,我们就减少这条边的容量,避免下次流爆这条边。

Hacked!如果我们不幸访问了 \(1\to 2\to 3\to 4\),你就寄了。明明有 \(maxflow=2\),我们却只有 \(1\),到底是什么地方出了问题?

观察到,不一定每条路径流过去都是最优的,那么我们加一条反悔边:当一条边 \((u,v)\) 流过了 \(w\) 的流量,我们就添加一条反向边 \((v,u)=w\)。如果以后我们发现走 \((u,v)\) 是不优的,我们就反悔,把这条路径分裂开,但是最大流不变。例如:

想象我们已经流了两个流量 \(1\to 2\to 3\to 4\),加上 \((2,1),(3,2),(4,3)\) 这些反悔边。现在试图走 \(1\to 3\to 2\to 4\),有了这个反悔边就可以成功流两个流量,现在真实的路径已经成为了 \(1\to 3\to 4\)\(1\to2\to4\),达到了 \(maxflow=4\)。再来一个:

我们走了 \(1\to2\to3\to4\) 流了两个流量,现在试图走 \(1\to3\to2\to4\),是可以走过去的,且 \((3,2)\) 的反悔边没反悔完,所以最后实际上是拆成三条路径 \(1\to2\to3\to4\)\(1\to3\to4\)\(1\to 2\to 4\),最终 \(maxflow=3\)

总而言之,它是正确的!于是我们可以开始模拟这个过程,得到了 FF 算法,它的复杂度是 \(O(Fm)\) 其中 \(F=maxflow\)。这样的复杂度随便卡。

0x02 最大流:Edmord-Karp 算法

为什么 FF 算法这么慢?

考虑优化一下,每次不要流一个流量这么少,我们使用 BFS,随便找一条路径,记录一路上最小的容量(不要走没有容量的边,可以当作不存在),再倒着回去流过来,这就是 EK 算法。复杂度 \(O(nm^2)\) 但跑不满。

code

0x03 最大流:Dinic 算法

为什么 EK 算法慢?我们发现,EK 一次只增广一条路径。考虑多条路径同时增广,我们先把图变成 DAG,一种可行的方法是求出每个节点的深度(BFS),增广时只走 \(dep_v=dep_u+1\) 的边,这样就没有环了,于是可以这样模拟增广的过程。

这就是 Dinic 算法,复杂度 \(O(n^2m)\)。为了吊打 EK,我们可以加入一些优化:

优化 1:当前弧优化

首先放一下原始代码:

LL dfs(int u,LL flow,int t){
	if(u==t||!flow) return flow;
	LL rest=flow;
	for(int i=g.head[u];i;i=g.nxt[i]){
		int v=g[i].v;
		if(!g[i].v||dep[v]!=dep[u]+1) continue;
		if(LL ans=dfs(v,min(rest,g[i].w),t)){
			g[i].w-=ans,g[i^1].w+=ans;
			rest-=ans;
            if(!rest) break;
		}
	}
	return flow-rest;
}

注意到一个点是会被重复走的(DAG),考虑第一次访问点 \(u\),它会放掉一部分流量给下一些点 \(v\),这些点 \(v\) 显然已经流完了,再给他们流量是没有意义的,所以第一次点 \(u\) 访问过的点 \(v\) 不需要再走,可以跳过。实现到代码里就是加一个 &,跳下一条边的同时删边。

注意,当前弧优化要把 if(!rest) break; 移到 if 里面,这条边可能没有流满,下一次仍要访问。

优化 2:炸点优化

考虑一个点 \(u\),它真的流不出流量,重复访问它是没有意义的,这时我们可以把这个点炸了,当它不存在。

code

template

0x04 最小费用最大流:EK/Dinic + SPFA = SSP

现在我们加入了费用!

怎么反悔?反悔可以理解成退钱,所以我们反悔可以把之前给的钱减掉,用一个负数即可。

考虑继续使用 EK 算法,现在我们不仅要管流量,还要管费用。可以贪心地选一条 \(S\to T\) 的最短路,把流量从这条最短路流过去,这就是最小费用最大流。

注意到费用可能为负(你自己定义的反悔边),可以使用 Bellman-Ford,复杂度是窒息的 \(O(Fnm)\)。如果有负环,SSP 将直接去世,需要消圈算法。

code EK + SPFA

template Dinic + SPFA

0x05 最小费用最大流:Dinic + Johnson 最短路 = 原始对偶

回忆一下我们怎样在负权图上跑 Dijkstra。

Recall:Johnson 全源最短路

首先我们跑一次 Bellman-Ford,判一下有没有负环,同时求出点 \(S\) 到点 \(u\) 的最短距离 \(h_u\)(距离定义为每条边的费用)。接下来是关键:将边 \((u,v)=w\) 的权值重定义为 \(w+h_u-h_v\)。最后跑 Dijkstra,所有 \(dis_u\) 更改为 \(dis_u-h_S+h_u\)。有几个问题:

为什么这些边有非负边权?考虑 \(h_u\) 是最短路长度,根据三角形不等式有 \(h_u+w(u,v)\geq h_v\),移项有 \(w(u,v)+h_u-h_v\geq 0\),证毕。

为什么最终答案为 \(dis_u-h_S+h_u\)?考虑这其实是错位相减,对于一条路径,前面的 \(-h_v\) 被后面来的 \(+h_u\) 抵消,最后剩下 \(h_S-h_u\),我们减掉这一部分就好了。

回到网络流

现在我们会了 Johnson,有个问题是怎么更新 \(h_u\),可以用这一次跑出来的最短路更新(\(h_u=dis_u-h_S+h_u\),注意到 \(h_S=0\))。考虑套上 Dinic,多路增广在最短路图上进行。我们需要换一下炸点优化,当前点 \(u\) 在栈里的时候炸掉它(\(vis_u=1\)),最后访问完再重构它(\(vis_u=0\)),如果它真的流不出去,炸了,这样就正确了。

细节

在最短路图上要怎么判断呢?如果先写 for(int i=1;i<=n;i++) h[i]+=dis[i];,那就要写 \(w(u,v)+h_u-h_v=0\) 为在最短路图上。原来是 \(dis_u+w(u,v)+h_u-h_v=dis_v\),移项并合并同类项后就是这个式子。

复杂度 \(O(Fm\log m)\),但实际表现不是很好(太长了)。

code

说句闲话,SSP 和原始对偶的区别在于最短路的求法,与增广路算法无关。应该是的。

0x10 网络流基本套路

作为一种模型,网络流有非常多的应用。

0x11 常见 trick

  • 多源汇:建立超级源点和汇点。
  • \(u\) 自带流量 \(w\):连边 \((S,u)=w\)
  • \(u\) 最终需要接受流量 \(w\):连边 \((u,T)=w\)
  • 经过点 \(u\) 有容量限制 \(w\):将这个点拆成 \(u,u’\),一个入点(连边 \((v,u)\)),一个出点(连边 \((u’,v)\)),中间连 \((u,u’)=w\)
  • 无向图:连两条有向边,现在你有了四条边哦。

0x12 最小割及应用

在网络图 \(G=(V,E)\) 中,割被定义为一种点集的划分方式:将所有的点划分成两个集合 \(s,t\),满足 \(s\cup t=V,s\cap t=\varnothing,S\in s,t\in T\)。这个割的权值被定义为 \(\forall(u,v)\in E,u\in s,v\in t\)\(w(u,v)\) 之和。

最大流-最小割定理:在任意网络中,最大流等于最小割。

证明

我们记最大流是 \(\max flow\),最小割为 \(\min cut\)。根据常识:

\[\max flow=\min cut\Leftrightarrow \begin{cases} \max flow\leq \min cut\text{ (I)}\\ \max flow\geq \min cut\text{ (II)}\\ \end{cases}\]

对于 \(\text{(I)}\) 式:对于任意一个割 \(cut\),最大流不会超过这个割的权值(权值是容量,不能比容量还大),所以 \(\max flow\leq cut\Rightarrow\max flow\leq \min cut\)

对于 \(\text{(II)}\) 式,对于一个最大流 \(flow\),在残量网络上 \(S,T\) 必然不连通(否则 \(flow\) 不是最大流),我们把导致不连通的边(满流的边)拿出来做一个割,则割的权值不超过最大流,即 \(\max flow\geq cut\Rightarrow\max flow\geq \min cut\)

所以 \(\max flow=\min cut\),证毕。

方案

用你喜欢的最大流算法跑出一个最大流,沿着残量网络从 \(S\) 找到的点就属于点集 \(s\)

注意,最大流和最小割只有数值相等的关系(正如 SAM 和后缀树只有形态相似的关系),建最小割的图时不能按照网络流思想建。

模型 1:两者选其一

考虑你有 \(n\) 个变量 \(x_u\),第 \(u\) 个变量有 \(m\) 种取值 \(x_u=c_{u,i}\),每一个取值就是它的代价。当一个变量取到某些值时,在给另一个变量取另一些值时要多给一些代价(当 \(x_u=c_{u,i}\) 时,若 \(x_v=c_{v,j}\),则要多支付一定的代价)。这就是“两者选其一”模型。不如叫它“多者取其一”?

建图方法:想象有 \(n\) 条链,每条链有 \(m\) 条边,第 \(u\) 条链的第 \(i\) 条边是 \(c_{u,i}\),两种:

  • 如果选了 \(c_{u,[1,i]}\) 再选 \(c_{v,[j,n]}\),连边 \((c_{u,[1,i]},c_{v,[j,n]})=w\)(如果不能选就是 \(\infty\))。常见变种:建一条反链,两个区间重叠,无向边等等。
  • 如果物品不止一个,新建一个点 \(p\),连边 \((p,T)=w\),将限制的 \(u\) 连边 \((u,p)=\infty\)

原文地址:http://www.cnblogs.com/caijianhong/p/16863491.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性