一些网络流算法

对于一类数据范围较小的 规划 / 分配 / 调整求最值问题,可以考虑建图用网络流处理。

可以和优化建图等手段一起使用。

memset 的时候要 注意范围(点的个数)。

概念

  1. 流网络:一个包含点集和边集的有向图 G=(V,E),边 (u,v) 有其属性 c(u,v),称为容量。图中有两个特殊顶点源点 s 和汇点 t
  2. 净流:边 (u,v) 的净流为 (u,v) 的实际流减去 (v,u) 的实际流。
  3. 可行流:记 f(u,v) 为边 (u,v) 的净流,满足以下条件的流称为可行流:
    1. 容量限制:0f(u,v)c(u,v)
    2. 流守恒:除 u=su=t 的情况外,xVf(u,x)=xVf(x,t)
  4. 流量值:令 f 为一个方案,表示每条边的取值。对于某一可行流而言,其流量值用 |f| 表示。|f|=(s,v)Ef(s,v)(v,s)Ef(v,s)
  5. 残量网络(残留网络):残量网络总是针对原图中某一可行流而言,因此残量网络可以视作可行流的一个函数,通常记为 Gf=(Vf,Ef),其中 Vf=VEfEE 的所有反向边。残量网络中的流量记为 c(u,v),定义为

c(u,v)={c(u,v)f(u,v)(u,v)Ef(v,u)(v,u)E

  1. 最大流(最大可行流):图中流量值最大的可行流。
  2. 增广路径:残量网络中从源点 s 到汇点 t 的简单路径。
  3. 割:网络中顶点的一种划分,把所有顶点划分成两个顶点集合 ST,满足 sS,tT,且 ST=V,ST=,记为割 (S,T)
  4. 割的容量:定义割 (S,T) 的容量 c(S,T)=uS,vTc(u,v),也可以用 c(s,t) 表示。
  5. 最小割:图中容量最小的割。

最大流

P3376 【模板】网络最大流

求最大流可以用 Edmonds-Karp, Dinic, ISAP, HLPP 等算法。

EdmondsKarp

EK 算法。

EK 算法的主要思路是在原图的残量网络中不断找增广路,直到原图中不存在增广路为止。

每次找到一条增广路,假设该增广路上最小的剩余容量为 k,则源点通过该增广路流出 k 个单位的流量到汇点。不妨存图时直接将每条边的剩余容量存下,增广时直接将增广路上的边权减去 k 即可。

直接在原图中找增广路会导致结果不是最大流,原因是找增广路时可能会先遍历到较劣方案,而减去边权会对残量网络造成影响。解决方法是在原图中添加初始容量为 0 的反向边,每次增广时将增广路径上每条边的反向边剩余容量加上 k

以下图为例,源点为 1,汇点为 4,尝试模拟 EK 算法。

最坏情况下会找到增广路 1,2,3,4,增加流量 1

如果没有反向边,此时已经无法找到增广路,得到最大流为 1,实际上该图最大流为 2。加入反向边后,发现增广路 1,3,2,4,增加流量 1

此时残量网络中无增广路,算法结束,得到最大流为 2

图中的反向边起到反悔作用。当选择第一条增广路后,我们发现实际上有更优的做法。所以通过 32 这条反向边将 1 个单位流量退回给顶点 2,由 1,2,4 这条增广路接管 1 个单位流量。同时原本边 3,4 的流量贡献给了增广路 1,3,4,成功将原本的较劣方案替换为最优方案。边权不为 1、顶点个数增加的情况同理。

EK 算法的时间复杂度为 O(m2n),但一般跑不到这个上界

Dinic

Dinic 算法是 EK 算法的优化。

我们发现 EK 算法每次可能会遍历整个残量网络,但只能找出一条增广路。这种单路增广的方式效率低下,因此考虑用多路增广优化。

前置知识:分层图

这里的分层图不是将原图复制成若干份,而是将原图划分成若干个层次。

du 为起点 s 到达顶点 u 需要经过的最少边数,表示该点的层次。由满足 dv=du+1 的边构成的子图称为分层图。显然分层图是一个有向无环图。

不妨先在残量网络上进行 bfs,求出每个顶点的层次。然后从源点开始搜索,(u,v)Ef,规定只有当 dv=du+1 时才能递归到顶点 v 找增广路,即在残量网络构造的分层图上找增广路。搜索到顶点 t 时说明找到了增广路,返回到达当前顶点的流量,回溯更新剩余容量。

当前弧优化:显然一条边在搜索结束后不会再次对答案产生影响,因此可以记录下当前顶点访问到的边。下一次访问该顶点直接从这条边开始搜索即可。

时间复杂度 O(n2m)

Dinic 求二分图最大匹配的时间复杂度是 O(mn)

ISAP

ISAP 也是优化的 Dinic 算法,但是与 Dinic 算法的思路不同。Dinic 算法选择多路增广优化,而 ISAP 仍然是单路增广。

类似地,从汇点 t 出发进行 bfs,构造出原图的分层图。规定只有当 du=dv+1 时,才能将 (u,v) 加入增广路。但是这样做可能会导致相邻点的层次相等,所以若当前点 u 无法继续增广时,尝试将当前顶点的层次抬高 1,使得剩余的流量可以继续被增广。

当源点的层次大于点数时,说明已经无法找到合法的增广路,算法结束。

gap 优化:令 gapi 表示层次为 i 的顶点个数。令抬高前的层次为 k,若抬高后 gapk=0,说明此时顶点可以分成两部分:层次小于 k 的和层次大于 k 的。又因为规定的增广条件,所以此时一定无法到达 t,直接结束算法。

同样地,ISAP 算法可以使用当前弧优化。

HLPP

最高标号预流推进(High Level Preflow Push, HLPP)是一种高效的预流推进算法。

假设顶点 u 有其高度 hu,初始时 hs=n,ht=0,其余顶点高度为其到 t 的最短路长度。规定流量只向下一层流,即推送流量只能经过 (u,v)E,满足 hu=hv+1 的边。HLPP 的核心即为不断尝试抬高每个点的高度,将其剩余的流量推送到下一层的顶点。

假设当前顶点 u 仍有剩余流量无法推送,则尝试对 u 重标号。显然可以令 hu=min(hv+1),其中 (u,v)E。如果最后该点高度被抬高到 n+1,说明该点的剩余流量都无法流出,此时会回流给源点。

每次用队列保存需要推送流量的顶点,每次推送完当前顶点的流量后将与其相邻的顶点加入队列。将队列换成优先队列就是 HLPP

gap 优化:不同于 ISAPHLPPgap 优化是若点 u 被抬高,其原本高度为 k,当抬高点 u 后不存在高度为 k 的顶点,则可以将令 hi>k,令 hi=n+1

该算法的时间复杂度是 O(n2m)

最小割

最小割最大流(Max-flow Min-cut) 定理:对于一个网络 G,有其最小割容量等于最大流流量。

对于网络 G 的任一可行流 f 和任一割 (S,T),总有其汇入 t 的流量为 uS,vTf(u,v)f(v,u),即 ST 的净流。显然上式小于等于 uS,vTc(u,v),即最小割容量。因此有任一可行流小于等于任一割,由此知最大流流量小于等于最小割容量。

显然任一割的容量等于最大流流量时取最小值,则我们可以构造一个符合条件的割:在原图中跑完最大流后,将 s 通过残量网络可以到达的点集设为 S,其余设为 T,那么 (S,T) 就是一组可行的最小割。原因是从 ST 的边一定满流,则 uS,vTf(u,v)f(v,u)=c(u,v),即最大流流量等于最小割容量。因此证明了最小割容量可以取到下界(最大流流量)。

综上所述,对于任意网络 G,总有其最小割容量等于最大流流量。

费用流

简介

P3381 【模板】最小费用最大流

给定一个包含 n 个点 和 m 条边的有向图(下称网络)G=(V,E),点编号为 1n,边编号为 1m,其中该网络的源点为 s,汇点为 t。网络上的每一条边都有其流量限制 w(u,v) 和单位流量的花费 c(u,v)

试给每条边 (u,v) 确定其流量 f(u,v),使得:

  1. 每条边的流量不超过其流量限制

  2. 除源点和汇点外,每个点流入的流量和流出的流量相等

  3. 源点流出的流量等于汇点流入的流量

定义网络 G 的流量 F(G)=(s,i)Ef(s,i),费用 C(G)=(i,j)Ef(i,j)×c(i,j)

求该网络的最小费用最大流,即在 F(G) 最大的前提下,令 C(G) 最小。

SPFA

SPFA 算法基于 EK 算法。

对于原图中的边 (u,v),在建反向边时令其容量为 0,单位流量的花费为 c(u,v)。以单位流量的花费为边权,求最小费用最大流只需将 EK 算法中用 bfs 找增广路改为用 SPFA 求最短路即可。

原理:

显然增广的顺序不影响最大流的值。因为求最短路实质是找增广路,且算法会在程序中无增广路时结束,因此该算法求出的 F(G) 是最大流。

该算法每次增广一条残量网络中的最短路,因此总是贪心地做出当前最优的选择。当贪心不影响其后的最短路时,显然局部最优可以导致全局最优。反之,因为建边时反向边的边权(单位流量的花费)为其正向边的相反数,所以之后找到的最短路可以通过这条边返还若干个单位流量,同时相应地撤销其花费。类似于最大流,这些流量实际上是被两条不同的增广路接管了。通过反向边,我们确定贪心可以反悔,因此该算法求出的 C(G) 是最小费用。

综上所述,该算法求出的是最小费用最大流。

当然,该算法也可以套 SPFA 的各种优化以及 Dinic

zkw

思想

zkw 流的思想和 KM 算法类似,且是一种连续最短路算法。

回顾最短路算法中的距离标号。定义 Di 为顶点 i 的距离标号,任何一个最短路算法保证对于任意 (u,v)E,有:

  1. DvDu+w(u,v)

  2. 对于每一个 v 一定存在至少一个 u 使得等号成立。

zkw 流中,我们同样以单位流量的花费为边权。若每次只沿满足 Du=Dv+c(u,v) 的边增广,显然条件 1 仍然成立,但是条件 2 不一定。在 KM 算法中,我们通过修改顶标扩大相等子图。类似地,我们可以通过合理地修改顶点的距离标号,使得算法可以继续增广。

对于最后一次找增广路失败的 dfs,令此时可以访问的点集为 V。找到 d=miniV,jV(c(i,j)Di+Dj),并令所有访问过的顶点距离标号增加 d

分析

个人理解为 zkw 流是以汇点为最短路的起点,求 d=miniV,jVc(i,j)Di+Dj,实际上是在找最短路上与当前相等子图距离最小的顶点 j。距离标号 Di 实际上保存的是 ij 的距离。当 d 取最小值时,每个顶点都会尽量靠近最短路的起点 t,也就相当于求最短路。当然,如果有多个满足要求的顶点,那么这些顶点都会被拉进子图。

根据上文,当 t 被加入 Di=Dj+cij 子图时即为找到了一条最短的增广路,显然此时 Di 表示从 it 的最短距离,即 Ds 表示流 1 个单位流量到 t 的最小花费。令流量为 flow,最小费用加 Ds×flow,最大流加 flow 即可。

经过大量实测,zkw 流一般在稠密图上比较快,在稀疏图上比较慢。

不妨从算法角度分析一下。zkw 流的核心优化在于采用了 KM 算法的重标号方式。每次标号只需要遍历一次边,复杂度比 SPFA 优。并且,zkw 流采用多路增广,即一次重标号后可以跑多次找增广路。

zkw 流的劣势在于 KM 算法的重标号方式不保证每次重标号后都一定存在增广路。最坏情况下,一次重标号只能往子图中拉进一条边。此时该算法就会反复重标号并且尝试增广,造成了时间的浪费。

由此看来,zkw 流的时间效率和路径费用有很大关系。对于最终流量较大而费用取值范围较小的图,或者是增广路经比较短的图(如二分图),zkw 流的效率更优。反之,如果费用取值范围较大且增广路径较长的图,zkw 流的效率就会显著下降。

原始对偶

h(u1)h(uk) 确定时,新图中长度为 l 的最短路可以对应一条原图中长度为 lh(u1)+h(uk) 的最短路。

考虑势函数 h 的维护。当原图中不存在负权边时,初始可以 1in,h(i)=0。反之,应该跑一次 SPFA,令 h 初始值为该顶点的距离标号。

每次找最短路结束后,令新图上从源点 s 到顶点 i 的距离为 disi,则 1ini 为访问过的顶点,令 h(i)=h(i)+disi

每次增广结束后原图中会加入一些边 (v,u),而这些边的反向边在被增广的最短路上,所以必定有 disu+w(u,v)=disv。对上式进行一些简单变形:

disu+w(u,v)=disvdisu+w(u,v)+h(u)h(v)=disv(disv+h(v))(disu+h(u))w(u,v)=0(disv+h(v))(disu+h(u))+w(v,u)=0

因此对于新加入的边,这样修改势函数仍然满足势函数的性质。

对于 G 原本存在的边 (u,v),根据最短路的性质有 disu+w(u,v)disv,简单变形:

disu+w(u,v)disv0disu+w(u,v)disv+h(u)h(v)0(disu+h(u))(disv+h(v))+w(u,v)0

因此对于 G 中原本存在的边,这样修改势函数也满足势函数的性质。

由此可以得到算法的流程:

  1. 初始化 h

  2. 在根据残量网络构建的新图中跑 Dijkstra

  3. st 存在可行路径,增广该路径并修改势函数,转 2;反之,算法结束。

posted @   kymru  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示
主题色彩