重修最大流 —— Ford–Fulkerson 增广与 Push-Relabel 预流推进
写在前面
先说明一点,上面提到的这两个都是一类算法的总称,不是算法。
还有就是由于篇幅原因时间复杂度我都没给证明。
Ford-Fulkerson 增广
增广路
我们定义 \(cf_{(u,v)} = c_{(u,v)} - f_{(u,v)}\),称之为流量差,即剩余流量。
增广路就是一条 \(s\) 到 \(t\) 的路径,满足其经过的所有边 \(cf_{(u,v)}>0\)。换句话说,这条路径还可以流更多的流量。
我们考虑贪心,把这条路径加入答案。但是这样不一定正确,比如(红色表示我们选择的增广路):

所以我们引入反悔贪心的思想,对每条边建一个反向边,并令 \(f_{(u,v)}=-f_{(v,u)}\)。那么我们走 \((v,u)\) 这条边就相当于退回了 \((u,v)\) 的部分流量,将其转移到另一个地方。
比如上面那张图,我们将节点分别编号为 \(1\sim 4\),则我们选择的路径为 \(1\to 4\),实际更优的为 \(1\to 2,3\to 4\)。而我们寻找增广路时又会找到 \(3\to 4\to 1\to 2\)(注意中间走的是反向边),\(4\to 1\) 和 \(1\to 4\) 的流量抵消,所以两种方法等价。
所以现在我们无需担心会按错误的顺序寻找增广路了。但是还有一个问题:为什么没有增广路就一定是最大流?
最大流最小割定理
首先先引入一个引理:对于一个任意的流 \(f\) 和割 \({S,T}\),都有 \(|f|\le||{S,T}||\)。直接推式子即可。
为了取等,显然第一个不等号需要 \(\{(u,v)|u\in T,v\in S\}\) 均为空流,而第二个不等号需要 \(\{(u,v)|u\in S,v\in T\}\) 均为满流。
然后我们来证明最大流最小割定理。
我们假设某一轮增广后不存在增广路,记所有能从 \(s\) 出发到达的点为集合 \(S\),其余的点为集合 \(T\)。那么显然 \({S,T}\) 是原图的一个割且有 \(||{S,T}||=\sum_{u\in S}\sum_{v\in T}cf_{(u,v)}=0\)(不然还存在增广路)。根据容量非负,我们可以得出 \(\{cf_{(u,v)}|u\in S,v\in T\}\) 均为 \(0\)。
将所有边分两类讨论:
- \((u,v)\in E\):此时 \(c_{(u,v)}-f_{(u,v)}=cf_{(u,v)}=0\),所以有 \(c_{(u,v)}=f_{(u,v)}\),即满流。
- \((v,u)\in E\):此时 \(c_{(u,v)}-f_{(u,v)}=0-f_{(u,v)}=f_{(v,u)}=c_{(v,u)}\),即空流。
满足我们刚才提到 \(|f|=||{S,T}||\) 的条件,所以最大流等于最小割。
增广停止后显然 \(f\) 满足上述条件。根据引理指出的关系,显然 \(f\) 是 \(G\) 的一个最大流,\({S,T}\) 是 \(G\) 的一个最小割。
复杂度分析
单次增广需要 \(O(|E|)\),最坏情况下需要增广 \(O(|f|)\) 次(\(f\) 为最大流),所以最坏总时间复杂度为 \(O(|E||f|)\),具体看你使用了什么算法。
EK 算法
是人都能想到。
每次 bfs 寻找一条增广路 \(p\),其剩余最大流量为 \(\min_{u,v\in p}\{cf_{(u,v)}\}\),然后将这些边的流量都加上这个值。重复操作直到不存在增广路为止。
时间复杂度 \(O(|V||E|^2)\)。
代码难度:简单;时间复杂度:高;实用度:一般;评价:拉完了。
Dinic 算法
这个才好用。
我们发现 EK 算法每次只能找一条增广路太慢了,所以我们考虑用更快的方法。最先想到的肯定是一次找多条增广路,但是这样可能会出一点问题(比如我们一直走一条边和它的反向边)。所以我们考虑限制一下。
使用 bfs 将原图分层,我们只能往下一层找增广路。每次可以找多条增广路,最后把它们并起来。
优化
当前弧优化
如果已知一条边不可能被增广路经过,那么我们不需要便利这条边。容易发现这些边一定遍历时先遍历到的边,所以可以用一个指针来维护。
多路增广
每次寻找新的增广路我们不需要从 \(s\) 重新开始,而是可以直接从当前节点开始。具体实现方法类似流量到达该节点后分成很多流量然后传给它的下一层节点。
注意事项
- Dinic 的流量只能给它下一层的节点而不是层数比它低的节点。
- 每次找完增广路记得重新分层,还要重置当前弧优化的指针(因为有可能换个方向某条边又能走了)。
复杂度分析
Dinic 的时间复杂度是 \(O(|V|^2|E|)\) 的。但是它在某些特殊情况下跑得很快。
在单位容量(即容量全部为 \(1\))的网络中,Dinic 的时间复杂度是 \(O(|E|min(|E|^{\frac{1}{2}},|V|^{\frac{2}{3}}))\)。如果每个非汇源的节点都满足入度或者出度为 \(1\),则时间复杂度是 \(O(|E||V|^{\frac{1}{2}})\) 的。二分图最大匹配就是一个比较常见的例子。
代码难度:简单;时间复杂度:一般;实用度:高;评价:夯爆了。
ISAP 算法
和 Dinic 的思想一样,但是我们边找增广路边重新分层。注意最开始的分层是在反向图上 \(t\) 到 \(s\) 进行的。
具体的,结束点 \(u\) 的增广后我们将其的层数设为它出边指向节点的最小层数 \(+1\),如果没有出边择设为 \(n\)。
容易发现,当 \(d_s\ge n\) 时不存在增广路,因为所有节点组成一条链都不行。
与 Dinic 类似,ISAP 也存在当前胡优化。
ISAP 还有一个优化就是记录每个层数的节点数,如果一个层没有节点说明断层了,那么就没有增广路了。
ISAP 的时间复杂度理论上与 Dinic 相同,但是实际中表现得更优秀。
代码难度:一般;时间复杂度:一般;实用度:一般;评价:人上人。
MPM 算法
好用不好写。
我们对于分层网络 \(L\) 中的节点 \(u\),我们定义:
我们记 \(p_u\) 最小的点为参照点。那么显然我们一定能找到一条增广路使得流量为 \(p_u\),因为这张图有向无环且其他点的容量都大于 \(p_u\)。那么我们直接 \(bfs\) 找到这条路径即可。
增广完之后所有满流边都可以从 \(L\) 中删除,因为它们不会在此阶段后被使用.同样,所有与 \(s\) 和 \(t\) 不同且没有出边或入边的节点都可以删除。
MPM 的时间复杂度是 \(O(|V|^3)\) 的,但是非常不好写(你们去 OI-wiki 上看就知道了)。
代码难度:极大;时间复杂度:较快;实用度:低;评价:人上人。
Rush-Relabel 预流推进
也要建反向边,方便把流量推送回 \(s\)。
通用的预留推进
我们不关心每个节点是否流量守恒,每次更新一个节点的信息来求解最大流。
我们定义一个节点的超额流:\(e_u=\sum_{(v,u)\in E}f_{(v,u)}-\sum_{(u,v)\in E}f_{(u,v)}\)。
高度函数
我们维护一个高度函数 \(h\) 满足:
- \(h(s)=|V|,h(t)=0\)
- \(\forall(u,v)\in E,h_u\le h_v+1\)
算法只会在 \(h_v=h_u+1\) 时推送。
推送
当节点 \(u\) 溢出且存在节点 \(v\) 使得 \(c_{(u,v)}>f_{(u,v)},h_u=h_v+1\) 时,我们尽可能多的把 \(u\) 溢出的流量推送给 \(v\),不关心 \(v\) 是否溢出。推送完成后如果 \((u,v)\) 满流则将其从残量网络中删除。
重贴标签
如果 \(u\) 溢出且 \((u,v)\in E,h_u\le h_v\),那么需要对节点 \(u\) 重贴标签。直接将其高度设为 \(min_{(u,v)\in E_f}(h_v)+1\) 即可。
初始化
令 \(h_s=|V|\),将所有 \(s\) 的出边都设为满流,然后将这些边删除。
具体过程
每次扫描整张图,如果需要操作就执行对应的操作即可。
代码难度:低;时间复杂度:不知道;实用度:低;评价:拉完了。
HLPP 算法
实际上只是每次选择高度最高的溢出节点操作。预流推进有相当一部分时间都花在重贴标签上,下面两种优化可以减少重贴标签的速度:
BFS 优化
初始化的时候将 \(h_u\) 设为 \(u\) 到 \(t\) 的最短路距离,\(h_s=n\)。顺便检查图的连通性,排除无解的情况。
GAP 优化
如果某个节点无法推送流量到下一层的节点,那么说明它已经不能把流量推送到 \(t\) 了,所以我们把它的高度设为 \(n+1\) 以尽快的把流量推送回 \(s\)。
时间复杂度是 \(O(n^2\sqrt{m})\) 的。
代码难度:一般;时间复杂度:较快;实用度:一般;评价:夯爆了。
例题
P2172
这不简单题吗。
每个位置建入点和出点,中间连边,容量为 \(1\) 表示只能经过一次,为 \(0\) 表示不能经过。
对于每个 \(R\times C\) 的矩阵,左上角的出点向右下角的入点连边,右上角向左下角连边,容量为 \(1\)。(实际上因为每个城市只能经过一次,所以每条边也最多经过一次)。
\(s\) 向所有入点连边,容量为 \(1\),所有出点连向 \(t\),容量为 \(1\),表示可以从任意位置开始,任意位置结束。(同理,这些边也只会经过一次)
直接跑最大流即可。注意到这是一个单位容量的网络,所以 Dinic 跑得非常快。
思路总结:拆点表示经过次数限制,经过边表示位置的转移。
P1129
这是好题。
如果我们把矩阵所有值为 \(1\) 的位置 \((i,j)\) 看做第 \(i\) 行和第 \(j\) 列有匹配的话,不难发现能达成最终状态的话一定是所有行和列都有匹配。(我们可以把和第 \(i\) 行匹配的列交换到第 \(i\) 列)。
那么我们把第 \(i\) 行和第 \(j\) 列连边,不难发现这是一张二分图,所以我们直接跑最大匹配即可。不会的建议自学一下。
思路总结:矩阵中考虑行和列的关系也是常用的方法。
P5771
我们模拟赛考到了这道题。
简单观察即可发现如果一个序列的奇偶性相同就一定是反质数序列。所以我们把相加产生质数的奇数和偶数连边,然后跑二分图最大独立集即可。
证明一下为什么这是二分图:首先如果存在奇环则奇环内一定有两个相邻的奇偶性相同的数,而奇偶性相同的数不会连边,矛盾。所以这张图不存在奇环,也就是二分图。
思路总结:和上一题一样,考虑两个元素的关系。
其实这道题我考场上想的是最小割做法,但是忘记最小割的定义了没做出来 QAQ。
总结
没有好的例题了怎么办 /ll。
其实你会发现例题讲的都是如何建模然后直接套最大流的板子 ,因为网络流就是这样的。
图论建模我会单独再写一个学习笔记。

浙公网安备 33010602011771号