0x3f的最大流笔记

Posted zhengzirui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0x3f的最大流笔记相关的知识,希望对你有一定的参考价值。

/* 网络流·总结·题单 ################################################################################

最大流等于最小割的证明 https://seineo.github.io/%E5%9B%BE%E8%AE%BA%EF%BC%9A%E6%9C%80%E5%A4%A7%E6%B5%81%E6%9C%80%E5%B0%8F%E5%89%B2%E8%AF%A6%E8%A7%A3.html

todo 网络流建模方式总结
 https://www.cnblogs.com/victorique/p/8560656.html
 https://blog.bill.moe/network-flow-models/
 NOI 一轮复习 I:二分图网络流 https://www.luogu.com.cn/blog/ix-35/noi-yi-lun-fu-xi-i-er-fen-tu-wang-lao-liu
 2016 国家集训队论文《网络流的一些建模方法》姜志豪 https://github.com/enkerewpo/OI-Public-Library/blob/master/IOI%E4%B8%AD%E5%9B%BD%E5%9B%BD%E5%AE%B6%E5%80%99%E9%80%89%E9%98%9F%E8%AE%BA%E6%96%87/%E5%9B%BD%E5%AE%B6%E9%9B%86%E8%AE%AD%E9%98%9F2016%E8%AE%BA%E6%96%87%E9%9B%86.pdf

todo 网络流 24 题 https://loj.ac/p?tagIds=30 https://www.luogu.com.cn/problem/list?tag=332
 线性规划与网络流 24 题 解题报告 https://byvoid.com/zhs/blog/lpf24-solution/

todo 题单 https://www.zybuluo.com/xzyxzy/note/992041
 网络流从入门到入土 #1 https://www.luogu.com.cn/training/12097#problems
 网络流从入门到入土 #2 https://www.luogu.com.cn/training/12098#problems
 网络流从入门到入土 #3 https://www.luogu.com.cn/training/12099#problems
 网络流建模经典题 https://www.luogu.com.cn/training/1230#problems
 网络流经典题目 https://www.luogu.com.cn/training/3144#problems

Max-Flow in almost linear time https://codeforces.com/blog/entry/100510

CF Tag https://codeforces.com/problemset?order=BY_RATING_ASC&tags=flows
*/

/* 最大流·建模·转换 ################################################################################

可视化 https://visualgo.net/zh/maxflow

https://en.wikipedia.org/wiki/Maximum_flow

建模·转换
将点拆为入点和出点(v 和 v+n),即可把点上的约束变成边上的约束
https://www.luogu.com.cn/problem/P2891 http://poj.org/problem?id=3281
【网络流 24 题】最长不降子序列 https://loj.ac/p/6005 https://www.luogu.com.cn/problem/P2766
    注意这题用到了操纵超级源点的技巧:容量限制与解除容量限制
NWERC07 B https://codeforces.com/gym/100723 http://poj.org/problem?id=3498 UVa12125 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=243&page=show_problem&problem=3277
网格模型 https://codeforces.com/problemset/problem/1360/G
https://codeforces.com/problemset/problem/546/E
转换 https://www.acwing.com/problem/content/2239/ http://poj.org/problem?id=1149
转换 https://codeforces.com/problemset/problem/653/D
todo 转换 https://atcoder.jp/contests/arc085/tasks/arc085_c

顶点上有容量
将顶点拆成两个(入顶点 x 和出顶点 y),入点向 x 连边,y 向出点连边,x 向 y 连边,容量为顶点的容量

无向图
视作两条容量均为 cap 的有向边(具体实现见下面代码中 addEdge 的注释)

多源汇最大流
建立超级源点 S 和超级汇点 T,S 向所有源点连边,所有汇点向 T 连边,每条边的容量为 inf 或对应源汇的容量限制
https://www.acwing.com/problem/content/2236/

只能经过这条边一次 ⇔ 容量为 1
http://poj.org/problem?id=2455 https://www.acwing.com/problem/content/2279/

上下界可行流·总结
https://oi-wiki.org/graph/flow/bound/
https://www.acwing.com/solution/content/17067/
https://zhuanlan.zhihu.com/p/324507636
todo 题单 https://www.luogu.com.cn/training/8462

无源汇上下界可行流(循环流)
假设存在一个流量守恒的解 f,通过将每条边的流量减去 low,得到一个新图的流,但其不一定满足流量守恒
对于每个顶点 v,记 d(v) = ∑lowIn(v) - ∑lowOut(v)
- 若 d(v) > 0,说明流入减去的更多,则需将 v 的流入量增加 d(v),这可以通过新建超级源点 S,并增加 S->v,容量为 d(v) 的边做到
- 若 d(v) < 0,说明流出减去的更多,则需将 v 的流出量增加 d(v),这可以通过新建超级汇点 T,并增加 v->T,容量为 -d(v) 的边做到
跑从 S 到 T 的最大流,若满流(即最大流等于从 S 出发的容量之和),则说明可以让新图的流量守恒,从而说明原图存在可行流 f,其每条边的流量为 low 加上新图中每条边的流量;若不满流则无解
模板题 https://loj.ac/p/115 https://www.acwing.com/problem/content/2190/

有源汇上下界可行流
从汇点向源点连一条容量为 inf 的边,即转换成了无源汇上下界可行流

有源汇上下界最大流
1. 跑一遍有源汇上下界可行流,若有解,记此时源点到汇点的流量为 f1(通过汇点向源点的反向边的流量得到)
2. 删去汇点到源点的边(或将其容量置为 0,具体实现时可以将汇点->源点边最后加入,或者使用指针记录该边及其反向边)
3. 在残余网络上继续增广,记额外的最大流为 f2,那么答案即为 f1+f2
模板题 https://loj.ac/p/116 https://www.luogu.com.cn/problem/P5192

有源汇上下界最小流
将上面第 3 步改成退流,即减去残余网络上从汇点到源点的最大流
模板题 https://loj.ac/p/117 https://www.luogu.com.cn/problem/P4843

分层图
注意:可以在原图的基础上添加边/增加容量,然后继续寻找增广路增广
【网络流 24 题】星际转移 https://loj.ac/p/6015 https://www.luogu.com.cn/problem/P2754

关键边
关键边 v-w 需满足,在跑完最大流后:
1. 这条边的流量等于其容量
2. 在残余网络上,从源点可以到达 v,从 w 可以到达汇点(即从汇点顺着反向边可以到达 w)
http://poj.org/problem?id=3204 https://www.acwing.com/problem/content/2238/
具体实现见下面代码中的 EXTRA
*/

/* 最小割·建模·转换 ################################################################################

https://en.wikipedia.org/wiki/Max-flow_min-cut_theorem
最小割模型汇总 https://blog.csdn.net/qq_35649707/article/details/77482691
下面的 topic 参考胡伯涛《最小割模型在信息学竞赛中的应用》(PDF 见 https://github.com/EndlessCheng/cp-pdf)

求出最大流后,从源点出发在残余网络上 DFS,标记所有能够到达的点。遍历原边集 edges,若其中一端有标记,另一端没有标记,则这条边为最小割上的边

常用技巧:用容量为 inf 的边来防止割断

建模·转换
https://www.acwing.com/problem/content/2282/
平均边权最小 https://www.acwing.com/problem/content/2281/
点连通度 SEERC04 F https://codeforces.com/gym/101461 http://poj.org/problem?id=1966 UVa1660 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=825&page=show_problem&problem=4535
   https://en.wikipedia.org/wiki/Connectivity_(graph_theory)
   https://en.wikipedia.org/wiki/Menger%27s_theorem
LCP38/21春·战队赛F https://leetcode-cn.com/problems/7rLGCR/

最大权闭合图 Maximum Weight Closure of a Graph
https://en.wikipedia.org/wiki/Closure_problem
源点向所有正权点连边,容量为相应点权
所有负权点向汇点连边,容量为相应点权的相反数
原图边的容量为 inf(从而保证不会在最小割中)
最后用正权点总和减去源点到汇点的最小割即为答案
以「最大获利」这题来解释,割掉源点到正权点的边,意味着放弃对应用户的收益;割掉负权点到汇点的边,意味着建立对应基站
NOI06 最大获利 https://www.luogu.com.cn/problem/P4174
【网络流 24 题】太空飞行计划 https://loj.ac/p/6001 https://www.luogu.com.cn/problem/P2762

最大密度子图 Maximum Density Subgraph
https://en.wikipedia.org/wiki/Dense_subgraph
参考 https://www.luogu.com.cn/problem/solution/UVA1389
二分上下界:最小密度为 1/n,最大密度为 m
二分精度:任意两个密度不同的子图,其密度差 >= 1/n^2
todo NEERC06 H https://codeforces.com/gym/100287 https://codeforces.com/gym/100532 http://poj.org/problem?id=3155 UVa1389 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=446&page=show_problem&problem=4135

二分图最小点权覆盖集 Minimum Weight Vertex Covering Set (MinWVCS) in a Bipartite Graph
二分图最大点权独立集 Maximum Weight Vertex Independent Set (MaxWVIS) in a Bipartite Graph
建立一个源 s,向 X 部每个点连边;建立一个汇 t,从 Y 部每个点向汇 t 连边,把二分图中的边看成是有向的,
则任意一条从 s 到 t 的路径,一定具有 s->v->w->t 的形式(v∈X, w∈Y)。
割的性质是不存在一条从 s 到 t 的路径。故路径上的三条边 s-v, v-w, w-t 中至少有一条边在割中。
若人为地令 v-w 不可能在最小割中,即令其容量为正无限,
可将条件简化为 s-v, w-t 中至少有一条边在最小割中,这正好与点覆盖集限制条件的形式相符(边的两端点中至少一个在覆盖集内),
而目标是最小化点权之和,这恰好也是最小割的优化目标。
对于最大点权独立集,其等价于点权之和减去最小点权覆盖集。
【网络流 24 题】骑士共存 https://loj.ac/p/6226 https://www.luogu.com.cn/problem/P3355
todo https://codeforces.com/contest/808/problem/F
NEERC03 D https://codeforces.com/gym/100725 https://codeforces.com/gym/101651 http://poj.org/problem?id=2125
黑白染色转化成二分图 https://www.acwing.com/problem/content/2328/

最小割的可行边和必须边(所有最小割集的并集和交集)
跑最大流,然后求整个残余网络的 SCC,则有:
- 可行边:两端不在一个 SCC 内,即不存在另一条从 v 到 w 的路径
- 必须边:一端在 S 的 SCC 内,另一端在 T 的 SCC 内
AHOI09 https://www.luogu.com.cn/problem/P4126
*/

/* 费用流·建模·转换 ################################################################################

https://en.wikipedia.org/wiki/Minimum-cost_flow_problem MCFP
https://en.wikipedia.org/wiki/Assignment_problem
https://en.wikipedia.org/wiki/Network_simplex_algorithm

NOTE: 对于修改容量的情况,由于 EK 是基于最短路的贪心算法,不能像最大流那样直接在残余网络上继续跑,必须重新建图重新跑 EK
todo https://codeforces.com/problemset/problem/362/E

建模·转换
从源点连容量为 1 费用为 0 的边到集合 A 中各点
从集合 B 中各点连容量为 1 费用为 0 的边到汇点
集合 A 和 B 之间连边,容量为 inf,费用为 F(Ai,Bj),F 根据题意
这样跑 MCMF 得到的结果是匹配全部 A(或 B)的最小花费
LC2172 https://leetcode-cn.com/problems/maximum-and-sum-of-array/
https://codeforces.com/problemset/problem/1437/C
【网络流 24 题】运输问题 https://loj.ac/p/6011 https://www.luogu.com.cn/problem/P4015
【网络流 24 题】数字梯形 https://loj.ac/p/6010 https://www.luogu.com.cn/problem/P4013
【网络流 24 题】深海机器人 https://loj.ac/p/6224 https://www.luogu.com.cn/problem/P4012
k 取方格数 https://www.luogu.com.cn/problem/P2045 http://poj.org/problem?id=3422
    关键技巧:拆点时,从入点向出点连两条边,第一条边容量为 1,费用为点权,第二条边容量为 k-1,费用为 0
    这表示第一次经过该点时,可以把数取走,之后再经过时就不再计算
【网络流 24 题】餐巾计划 https://loj.ac/p/6008 https://www.luogu.com.cn/problem/P1251

最大费用
将每条边的费用反向,答案即为 -MCMF

无源汇上下界最小费用可行流
建图和上面的「无源汇上下界可行流」一样
NOI08 志愿者招募 https://www.luogu.com.cn/problem/P3980(也可以用线性规划做)
- 由于没有上界,建图的时候可以不用减去下界
- 把每天的人数要求看成是边的流量下界(从 i 天向 i+1 天连边)
- 由于要满足流量守恒,对于每个人 i,需要从结束日期向开始日期连边,容量为 inf,费用为 ci。这相当于每个人在流网络的一单位的流量流过了一个环
- 代码实现 https://www.luogu.com.cn/record/56398769
AHOI14/JSOI14 支线剧情 https://www.luogu.com.cn/problem/P4043
-「看完所有剧情」可以转换成每条边的流量下界为 1,容量为 inf,费用为过剧情花费的时间
-「开始新的游戏」可以转换成每个点向点 1 连边,容量为 inf,费用为 0
- 代码实现 https://www.luogu.com.cn/record/56402617

流通问题 circulation problem
最小费用流通问题 minimum-cost-circulation problem
https://en.wikipedia.org/wiki/Circulation_problem
The circulation problem and its variants are a generalisation of network flow problems,
with the added constraint of a lower bound on edge flows,
and with flow conservation also being required for the source and sink (i.e. there are no special nodes).
《算法导论》思考题 29-5
todo https://codeforces.com/contest/1455/problem/E
 https://codeforces.com/blog/entry/85186?#comment-728533
*/

// 最大流 Dinic\'s algorithm O(n^2 * m)  二分图上为 O(m√n)
// 如果容量是浮点数,下面代码中 > 0 的判断要改成 > eps
// https://en.wikipedia.org/wiki/Dinic%27s_algorithm
// https://oi-wiki.org/graph/flow/max-flow/#dinic
// https://cp-algorithms.com/graph/dinic.html
// 模板题 https://www.luogu.com.cn/problem/P3376 https://www.luogu.com.cn/problem/P2740
func (*graph) maxFlowDinic(in io.Reader, n, m, st, end int, min func(int, int) int) int 
	const inf int = 1e9 // 1e18
	st--
	end--

	type neighbor struct to, rid, cap, eid int  // rid 为反向边在邻接表中的下标
	g := make([][]neighbor, n)
	addEdge := func(from, to, cap, eid int) 
		g[from] = append(g[from], neighborto, len(g[to]), cap, eid)
		g[to] = append(g[to], neighborfrom, len(g[from]) - 1, 0, -1) // 无向图上 0 换成 cap
	
	for i := 0; i < m; i++ 
		var v, w, cp int
		Fscan(in, &v, &w, &cp)
		v--
		w--
		addEdge(v, w, cp, i)
	

	var d []int // 从源点 st 出发的距离
	bfs := func() bool 
		d = make([]int, len(g))
		d[st] = 1
		q := []intst
		for len(q) > 0 
			v := q[0]
			q = q[1:]
			for _, e := range g[v] 
				if w := e.to; e.cap > 0 && d[w] == 0 
					d[w] = d[v] + 1
					q = append(q, w)
				
			
		
		return d[end] > 0
	
	// 寻找增广路
	var iter []int // 当前弧,在其之前的边已经没有用了,避免对没有用的边进行多次检查
	var dfs func(int, int) int
	dfs = func(v int, minF int) int 
		if v == end 
			return minF
		
		for ; iter[v] < len(g[v]); iter[v]++ 
			e := &g[v][iter[v]]
			if w := e.to; e.cap > 0 && d[w] > d[v] 
				if f := dfs(w, min(minF, e.cap)); f > 0 
					e.cap -= f
					g[w][e.rid].cap += f
					return f
				
			
		
		return 0
	
	dinic := func() (maxFlow int)  // int64
		for bfs() 
			iter = make([]int, len(g))
			for 
				if f := dfs(st, inf); f > 0 
					maxFlow += f
				 else 
					break
				
			
		
		return
	
	maxFlow := dinic()

	// EXTRA: 容量复原(不存原始容量的写法)
	for _, es := range g 
		for i, e := range es 
			if e.eid >= 0  // 正向边
				es[i].cap += g[e.to][e.rid].cap
				g[e.to][e.rid].cap = 0
			
		
	

	// EXTRA: 求流的分配方案(即反向边上的 cap)
	// https://loj.ac/p/115 https://www.acwing.com/problem/content/2190/
	ans := make([]int, m)
	for _, es := range g  // v
		for _, e := range es 
			w, i := e.to, e.eid
			if i >= 0  // 正向边
				ans[i] = g[w][e.rid].cap
			
		
	

	// EXTRA: 求关键边(扩容后可以增加最大流的边)的数量
	// 关键边 v-w 需满足,在跑完最大流后:
	// 1. 这条边的流量等于其容量
	// 2. 在残余网络上,从源点可以到达 v,从 w 可以到达汇点(即从汇点顺着反向边可以到达 w)
	// http://poj.org/problem?id=3204 https://www.acwing.com/problem/content/2238/
	
		// 在残余网络上跑 DFS,看看哪些点能从源点和汇点访问到(从汇点出发的要判断反向边的流量)
		vis1 := make([]bool, len(g))
		var dfs1 func(int)
		dfs1 = func(v int) 
			vis1[v] = true
			for _, e := range g[v] 
				if w := e.to; e.cap > 0 && !vis1[w] 
					dfs1(w)
				
			
		
		dfs1(st)

		vis2 := make([]bool, len(g))
		var dfs2 func(int)
		dfs2 = func(v int) 
			vis2[v] = true
			for _, e := range g[v] 
				if w := e.to; !vis2[w] && g[w][e.rid].cap > 0 
					dfs2(w)
				
			
		
		dfs2(end)

		ans := 0
		for v, es := range g 
			if !vis1[v] 
				continue
			
			for _, e := range es 
				// 原图的边,流量为 0(说明该边满流),且边的两端点能分别从源汇访问到
				if e.eid >= 0 && e.cap == 0 && vis2[e.to] 
					ans++
				
			
		
	

	return maxFlow


// ISAP, Improved Shortest Augmenting Path O(n^2 * m)
// https://oi-wiki.org/graph/flow/max-flow/#isap
// https://www.renfei.org/blog/isap.html
// 测试了一下性能和 Dinic 差不多
func (*graph) maxFlowISAP(in io.Reader, n, m, st, end int) int 
	st--
	end--

	type neighbor struct to, rid, cap int  // rid 为反向边在邻接表中的下标
	g := make([][]neighbor, n)
	addEdge := func(from, to, cap int) 
		g[from] = append(g[from], neighborto, len(g[to]), cap)
		g[to] = append(g[to], neighborfrom, len(g[from]) - 1, 0)
	
	for i := 0; i < m; i++ 
		var v, w, cp int
		Fscan(in, &v, &w, &cp)
		v--
		w--
		addEdge(v, w, cp)
	

	// 计算从汇点 end 出发的距离
	d := make([]int, n)
	for i := range d 
		d[i] = -1
	
	d[end] = 0
	cd := make([]int, n+1) // 注意有 d[i] == n 的情况
	q := []intend
	for len(q) > 0 
		v := q[0]
		q = q[1:]
		cd[d[v]]++
		for _, e := range g[v] 
			if w := e.to; d[w] < 0 
				d[w] = d[v] + 1
				q = append(q, w)
			
		
	
	if d[st] < 0 
		return -1
	

	// 寻找增广路
	const inf int = 1e9 // 1e18
	maxFlow := 0        // int64
	iter := make([]int, n)
	type pair struct v, i int 
	fa := make([]pair, n)
o:
	for v := st; d[st] < n; 
		if v == end 
			minF := inf
			for v := end; v != st; 
				p := fa[v]
				if c := g[p.v][p.i].cap; c < minF 
					minF = c
				
				v = p.v
			
			for v := end; v != st; 
				p := fa[v]
				e := &g[p.v][p.i]
				e.cap -= minF
				g[v][e.rid].cap += minF
				v = p.v
			
			maxFlow += minF
			v = st
		
		for i := iter[v]; i < len(g[v]); i++ 
			e := g[v][i]
			if w := e.to; e.cap > 0 && d[w] < d[v] 
				fa[w] = pairv, i
				iter[v] = i
				v = w
				continue o
			
		
		if cd[d[v]] == 1 
			break // gap 优化
		
		cd[d[v]]--
		minD := n - 1
		for _, e := range g[v] 
			if e.cap > 0 && d[e.to] < minD 
				minD = d[e.to]
			
		
		d[v] = minD + 1
		cd[d[v]]++
		iter[v] = 0
		if v != st 
			v = fa[v].v
		
	
	return maxFlow


// 最高标号预流推进 (HLPP, High Level Preflow Push)   O(n^2 * √m)
// 注:虽然在复杂度上比增广路方法进步很多,但是预流推进算法复杂度的上界是比较紧的,因此有时差距并不会很大
// https://en.wikipedia.org/wiki/Push%E2%80%93relabel_maximum_flow_algorithm
// https://en.wikipedia.org/wiki/Push%E2%80%93relabel_maximum_flow_algorithm#Highest_label_selection_rule
// https://oi-wiki.org/graph/flow/max-flow/#hlpp
// https://www.luogu.com.cn/blog/ONE-PIECE/jiu-ji-di-zui-tai-liu-suan-fa-isap-yu-hlpp
// 模板题 https://loj.ac/p/127 https://www.luogu.com.cn/problem/P4722
// todo deque 优化 + 全局重贴标签等 https://www.luogu.com.cn/problem/solution/P4722
type hlppHeap struct 
	sort.IntSlice
	d []int


func (h hlppHeap) Less(i, j int) bool  return h.d[h.IntSlice[i]] > h.d[h.IntSlice[j]]  // 处于堆中的节点的 d 值不会改变,所以可以直接比较
func (h *hlppHeap) Push(v any)         h.IntSlice = append(h.IntSlice, v.(int)) 
func (h *hlppHeap) Pop() any           a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v 
func (h *hlppHeap) push(v int)         heap.Push(h, v) 
func (h *hlppHeap) pop() int           return heap.Pop(h).(int) 

func (*graph) maxFlowHLPP(in io.Reader, n, m, st, end int, min func(int, int) int) int 
	st--
	end--

	type neighbor struct to, rid, cap int  // rid 为反向边在邻接表中的下标
	g := make([][]neighbor, n)
	addEdge := func(from, to, cap int) 
		g[from] = append(g[from], neighborto, len(g[to]), cap)
		g[to] = append(g[to], neighborfrom, len(g[from]) - 1, 0)
	
	for i := 0; i < m; i++ 
		var v, w, cp int
		Fscan(in, &v, &w, &cp)
		v--
		w--
		addEdge(v, w, cp)
	

	// 计算从汇点 end 出发的距离
	d := make([]int, n)
	for i := range d 
		d[i] = -1
	
	d[end] = 0
	cd := make([]int, 2*n)
	_q := []intend
	for len(_q) > 0 
		v := _q[0]
		_q = _q[1:]
		cd[d[v]]++
		for _, e := range g[v] 
			if w := e.to; d[w] < 0 
				d[w] = d[v] + 1
				_q = append(_q, w)
			
		
	
	if d[st] < 0 
		return -1
	
	d[st] = n

	exFlow := make([]int, n)
	q := hlppHeapd: d
	inQ := make([]bool, n)
	push := func(v, f int, e *neighbor) 
		w := e.to
		e.cap -= f
		g[w][e.rid].cap += f
		exFlow[v] -= f
		exFlow[w] += f
		if w != st && w != end && !inQ[w] 
			q.push(w)
			inQ[w] = true
		
	
	// 将源点的所有边都满流地推送出去
	for i := range g[st] 
		if e := &g[st][i]; e.cap > 0 
			push(st, e.cap, e)
		
	
	for len(q.IntSlice) > 0 
		v := q.pop()
		inQ[v] = false
	o:
		for 
			for i := range g[v] 
				if e := &g[v][i]; e.cap > 0 && d[e.to] < d[v] 
					push(v, min(e.cap, exFlow[v]), e)
					if exFlow[v] == 0 
						break o
					
				
			
			dv := d[v]
			cd[dv]--
			if cd[dv] == 0  // gap 优化
				for i, h := range d 
					if i != st && i != end && dv < h && h <= n 
						d[i] = n + 1 // 超过 n,从而尽快将流量推回 st
					
				
			
			// relabel
			minD := int(1e9)
			for _, e := range g[v] 
				if w := e.to; e.cap > 0 && d[w] < minD 
					minD = d[w]
				
			
			d[v] = minD + 1
			cd[d[v]]++
		
	
	return exFlow[end]


// 无向图全局最小割
// Stoer-Wagner 算法 O(nm+n^2logn)
// https://en.wikipedia.org/wiki/Stoer%E2%80%93Wagner_algorithm
// https://algs4.cs.princeton.edu/code/edu/princeton/cs/algs4/GlobalMincut.java.html
// todo 模板题 https://www.luogu.com.cn/problem/P5632 http://poj.org/problem?id=2914
func (*graph) minimumCutStoerWagner(dist [][]int) int 
	panic("todo")


// 最小费用流 MCFP
// 最小费用最大流 MCMF(即满流时的费用)
// 将 Edmonds-Karp 中的 BFS 改成 SPFA O(fnm) 或 Dijkstra O(fmlogn)
// 要求初始网络中无负权圈
// 性能对比(洛谷 P3381,由于数据不强所以 SPFA 很快):SPFA 1.05s(max 365ms)   Dijkstra 1.91s(max 688ms)
// https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm
// https://oi-wiki.org/graph/flow/min-cost/
// https://cp-algorithms.com/graph/min_cost_flow.html
// 最小费用流的不完全算法博物馆 https://www.luogu.com.cn/blog/Atalod/zui-xiao-fei-yong-liu-di-fou-wan-quan-suan-fa-bo-wu-guan
// 模板题 https://www.luogu.com.cn/problem/P3381
func (*graph) minCostFlowSPFA(in io.Reader, n, m, st, end int) (int, int64) 
	const inf int = 1e9 // 1e18
	st--
	end--

	type neighbor struct to, rid, cap, cost, eid int  // rid 为反向边在邻接表中的下标
	g := make([][]neighbor, n)
	addEdge := func(from, to, cap, cost, eid int) 
		g[from] = append(g[from], neighborto, len(g[to]), cap, cost, eid)
		g[to] = append(g[to], neighborfrom, len(g[from]) - 1, 0, -cost, -1) // 无向图上 0 换成 cap
	
	for i := 0; i < m; i++ 
		var v, w, cp, cost int
		Fscan(in, &v, &w, &cp, &cost)
		v--
		w--
		addEdge(v, w, cp, cost, i)
	

	dist := make([]int64, len(g))
	type vi struct v, i int 
	fa := make([]vi, len(g))
	spfa := func() bool 
		const _inf int64 = 1e18
		for i := range dist 
			dist[i] = _inf
		
		dist[st] = 0
		inQ := make([]bool, len(g))
		inQ[st] = true
		q := []intst
		for len(q) > 0 
			v := q[0]
			q = q[1:]
			inQ[v] = false
			for i, e := range g[v] 
				if e.cap == 0 
					continue
				
				w := e.to
				if newD := dist[v] + int64(e.cost); newD < dist[w] 
					dist[w] = newD
					fa[w] = viv, i
					if !inQ[w] 
						q = append(q, w)
						inQ[w] = true
					
				
			
		
		return dist[end] < _inf
	
	ek := func() (maxFlow int, minCost int64) 
		for spfa() 
			// 沿 st-end 的最短路尽量增广
			minF := inf
			for v := end; v != st; 
				p := fa[v]
				if c := g[p.v][p.i].cap; c < minF 
					minF = c
				
				v = p.v
			
			for v := end; v != st; 
				p := fa[v]
				e := &g[p.v][p.i]
				e.cap -= minF
				g[v][e.rid].cap += minF
				v = p.v
			
			maxFlow += minF
			minCost += dist[end] * int64(minF)
		
		return
	
	return ek()


// 基于原始对偶方法 (primal-dual method)
// https://blog.xehoth.cc/DurationPlan-Primal-Dual/
func (*graph) minCostFlowDijkstra(in io.Reader, n, m, st, end, flowLimit int) int64 
	st--
	end--

	type neighbor struct to, rid, cap, cost int 
	g := make([][]neighbor, n)
	addEdge := func(from, to, cap, cost int) 
		g[from] = append(g[from], neighborto, len(g[to]), cap, cost)
		g[to] = append(g[to], neighborfrom, len(g[from]) - 1, 0, -cost)
	
	for i := 0; i < m; i++ 
		var v, w, cp, cost int
		Fscan(in, &v, &w, &cp, &cost)
		v--
		w--
		addEdge(v, w, cp, cost)
	

	h := make([]int64, len(g)) // 顶点的势
	dist := make([]int64, len(g))
	type pair struct v, i int 
	fa := make([]pair, len(g))
	dijkstra := func() bool 
		const _inf int64 = 1e18
		for i := range dist 
			dist[i] = _inf
		
		dist[st] = 0
		q := dijkstraHeapst, 0
		for len(q) > 0 
			p := q.pop()
			v := p.v
			if p.dis > dist[v] 
				continue
			
			for i, e := range g[v] 
				if e.cap == 0 
					continue
				
				w := e.to
				if newD := dist[v] + int64(e.cost) + h[v] - h[w]; newD < dist[w] 
					dist[w] = newD
					fa[w] = pairv, i
					q.push(dijkstraPairw, newD)
				
			
		
		return dist[end] < _inf
	
	minCost := int64(0)
	for flowLimit > 0 && dijkstra() 
		for i, d := range dist 
			h[i] += d
		
		minF := flowLimit // inf
		for v := end; v != st; 
			p := fa[v]
			if c := g[p.v][p.i].cap; c < minF 
				minF = c
			
			v = p.v
		
		for v := end; v != st; 
			p := fa[v]
			e := &g[p.v][p.i]
			e.cap -= minF
			g[v][e.rid].cap += minF
			v = p.v
		
		flowLimit -= minF               // maxFlow += minF
		minCost += h[end] * int64(minF) // 注意这里是 h 不是 dist
	
	if flowLimit > 0 
		return -1
	
	return minCost


// todo 基于 Capacity Scaling 的弱多项式复杂度最小费用流算法 https://ouuan.github.io/post/%E5%9F%BA%E4%BA%8E-capacity-scaling-%E7%9A%84%E5%BC%B1%E5%A4%9A%E9%A1%B9%E5%BC%8F%E5%A4%8D%E6%9D%82%E5%BA%A6%E6%9C%80%E5%B0%8F%E8%B4%B9%E7%94%A8%E6%B5%81%E7%AE%97%E6%B3%95/

// ZKW 费用流
// https://artofproblemsolving.com/community/c1368h1020435

  

最大流学习笔记

1 基本的Ford-Fulkerson方法。该方法的思想就是每次找到一个增广路$p$,然后将增广路 $p$对应的流加到之前的流上得到新的流,一直这样直到找不到增广路,这时候找到的流就是最大流。

算法的伪代码如下

技术分享

假设容量是整数,最大流为$f^{*}$,那么while循环最多执行$|f^{*}|$次,因为每次至少使得流量增加1,每次找增光路的代价是$O(E)$,所以总的复杂度是$O(E|f^{*}|)$

 

2 Edmonds-Karp算法。Edmonds-Karp算法是对Ford-Fulkerson的改进,具体就是每次找增广路时找的是s到t的最短路(路径边长为1)。设$\delta_{f}(u,v)$表示残存网络中从$u$到$v$的最短路径

 

3 如果Edmonds-Karp算法运行在流网络G上,那么对所有的节点$v\in V-\{s,t\}$,残存网络$G_{f}$中的最短路径距离$\delta_{f}(s,v)$随着每次流量的递增而单调递增。

 

4 如果Edmonds-Karp算法运行在流网络G上,则该算法所执行的流量递增的总次数为$O(VE)$

 

5 Edmonds-Karp算法每次找最短路径的复杂度是$O(E)$,所以总的复杂度是$O(VE^{2})$

 

以下为证明

3的证明

假设对于某个节点$v\in V-\{s,t\}$,存在一个流量递增的操作使得源点到$v$的距离变小了。设$f$是最短路径减少之前的流量,对应的残存网络为$G_{f}$,$f^{‘}$是递增之后的流量,对应的残存网络为$G_{f^{‘}}$,设$v$为在所有最短距离减少的节点中,$\delta_{f^{‘}}(s,v)$最小的节点。有$\delta_{f^{‘}}(s,v)<\delta_{f}(s,v)$。

设$p=s\sim u\rightarrow v$为残存网络$G_{f^{‘}}$中从源节点s到$v$的一条最短路径,因此$(u,v)\in G_{f^{‘}}$,并且$\delta_{f^{‘}}(s,u)=\delta_{f^{‘}}(s,v)-1$

源节点到$u$的距离没有减少,所以$\delta_{f^{‘}}(s,u)\geq \delta_{f}(s,u)$

那么一定有$(u,v)\notin E_{f}$,否则:

$\delta_{f}(s,v)\leq \delta_{f}(s,u)+1$

$\leq \delta_{f^{‘}}(s,u)+1$

$=\delta_{f^{‘}}(s,v)$

这与$\delta_{f^{‘}}(s,v)<\delta_{f}(s,v)$矛盾。

那么现在$(u,v)\notin E_{f}$但是$(u,v)\in E_{f^{‘}}$,这一递增操作一定是增加了$v$到$u$的流量。又因为Edmonds-Karp算法总是沿着最短路径增加流,所以在$G_{f}$中从s到$u$的最短路径上的最后一条边是$(v,u)$,所以

$\delta_{f}(s,v)=\delta_{f}(s,u)-1$

$\leq \delta_{f^{‘}}(s,u)-1$

$=\delta_{f^{‘}}(s,v)-2$

这与$\delta_{f^{‘}}(s,v)<\delta_{f}(s,v)$矛盾。所以一开始的假设是不正确的。

 

4的证明

在$G_{f}$中,如果一条路径$p$的残存容量是该路径上的边$(u,v)$的残存容量,即$c_{f}(p)=c_{f}(u,v)$,那么我们称$(u,v)$为关键边。下面首先证明对于每条边来说,成为关建边的次数最多为$\frac{|V|}{2}$

当$(u,v)$第一次成为关键边时有$\delta_{f}(s,v)=\delta_{f}(s,u)+1$

之后边$(u,v)$将从残存网络中消失。到下一次$(u,v)$成为关键边时,一定在之前$(v,u)$出现在了路径$p$上,假设这一事件发生时$f^{‘}$是G的流,那么有

$\delta_{f^{‘}}(s,u)=\delta_{f^{‘}}(s,v)+1$

由于$\delta_{f}(s,v)\leq \delta_{f^{‘}}(s,v)$

那么有$\delta_{f^{‘}}(s,u)=\delta_{f^{‘}}(s,v)+1\geq \delta_{f}(s,v)+1=\delta_{f}(s,u)+2$,也就是到$u$的距离增加了至少2

初始时到$u$的距离至少为0,最后到$u$的距离最多为$|V|-2$($(u,v)$出现在增广路径上意味着$u\neq t$,同时$u\neq s$).所以在$(u,v)$第一次成为关建边后还最多能成为$\frac{|V|-2}{2}=\frac{|V|}{2}-1$次关建边,所以一共最多成为$\frac{|V|}{2}$次关建边。

一共有$|E|$条边,所以一共有最多$\frac{|V||E|}{2}$条关建边,每条增广路至少出现一条关建边,所以总次数为$O(VE)$

以上是关于0x3f的最大流笔记的主要内容,如果未能解决你的问题,请参考以下文章

最大流学习笔记

最大流学习笔记

MOOC运筹学不挂科笔记 网络最大流问题

最大流学习笔记-推送重贴标签算法一

NTU 课程笔记: 网络流

最大流学习笔记-前置重贴标签算法一