网络流
【什么是网络流】
一张带权图,给定了一个源点(起点)和一个汇点(终点)。
每个点比作一个中转站,源点比作一个水源,汇点比作一个洞。
每条边比作一条管道,边权比作上限。
现在要把水从源点输送到汇点,水经过若干中转站和管道到达汇点。
但是,每条管道单位时间内输送的水不能达到这条管道的上限(权)。
每个中转点所到达的水可以分流进各个管道里。
某种方案把水流到汇点的量,称作这种方案的流量。
网络流处理有关于流量的问题。
【最大流问题】
我们想要让最多的水可以流到汇点。
那么,最多有多少水可以流到汇点呢?
网络最大流算法负责处理这个问题。
【一个原始的想法】
我们的想法:能流的话,就流。
从这个图里随便找一条源点到汇点的路(增广路:一条权值都大于 \(0\) 的从源点到汇点的路径)。
这条路上的边的最小权记为 \(a\)。
把 \(a\) 单位的水沿着这条路流到汇点。
经过的所有边权,都减去 \(a\),表示它最多能输送的水又少了 \(a\)。
如果一条边的边权为 \(0\),就相当于没有了。(饱和了,不能通)
【这个想法的反例】
这个图的最大流很明显是上下分两条。但如果我们不幸搜到了中间岔开的一条,就会 WA。
而下面找到的一条路有可能会阻断上下两条。
所以,这个想法一旦选定了一条本不该选的边,就无可挽回了。
【Ford_Fulkerson】
上面说到,这个想法挂掉的原因在于不能选不该选的边。
那么,我们可以考虑让我们可以 “反悔”。
而这,就是 Ford_Fulkerson 算法思想,以下简称 FF算法。
(因为只给了理论没给程序,只能是思想而不是算法)
【FF算法】
考虑每条边再建一条反向的边。
开始时,每条反向边的边权均为 0 。
反向边的意义是:可以反悔的额度。
比如,还是这个图。
把反向边建出来,并加上权。
假如我们走了这条横跨两大陆的边,有几条黑边输送了 1 的水,要减 1.
与此同时,这些边的反向边要加 1,即可以反悔的额度加 1 了.
于是图变成这样:
我们惊奇地发现,如果加了反向边,还能找到一条路:
如果走了这条路,我们横跨大陆的错误就纠正了。
如何翻译 “走了这条路” ?
当你走了一条反向边,相当于把来到这里的水又倒了一些出来。
这些水 “反悔” 了,换了一条路,再走一遍。
【具体实现】
我们知道了整个算法的流程,但其中还有一个问题。
我们一直在说:随便找一条路,如何找呢 ?
【直接做:EK】
就按照 FF算法 说的,我们就每次找一条增广路,然后流过去。
也就是每次从源点搜索一条到汇点的路径,然后流。
这种算法就叫做 EK(Edmonds-Karp),复杂度 \(O(n^2m)\)
【图分层:Dinic】
我们进行一个优化:先从源点 BFS,只经过剩余容量 \(>0\) 的边,记录每个点的深度(距离)。
然后找增广路时,只走那些通向下一层的边。
【当前弧优化】
非常重要!
【有上下界的网络流】
【无源汇可行流】
建立超源点 \(S'\) 和超源点 \(T'\)。暂时只看每条边的下界,看每个点如果每条边都卡着下界,入度和出度比是多了还是少了。少了,就连一条到 \(T'\) 的边,容量为少了多少;否则 \(S'\) 连一条到这个点,容量为多了多少。
从 \(S'\) 到 \(T'\) 跑最大流,判断所有 \(S'\) 连出去的边是否都满流,如果不满流就不可行。
【有源汇可行流】
在上面图的基础上,在原来的 \(S,T\) 之间连一条 \(T\rightarrow S\) 的容量为 \(+\infty\) 的边,然后跑 \(S'\rightarrow T'\) 最大流,判断是否满流。
(这个时候我们找到了一个可行流,且这个可行流就是此时 \(T\rightarrow S\) 这条边的流量)
(想知道 \(T\rightarrow S\) 的流量,可以通过遍历 \(T\) 的所有出边,找到 \(T\rightarrow S\) 的边,然后通过 \(rev\) 看反边)
【有源汇最大流】
上面的图跑完最大流得到残余网络,在这个残余网络上先删掉 \(T\rightarrow S\) 的 \(+\infty\) 边后求 \(S\) 到 \(T\) 的最大流,这个流加上上面可行流的流量就是最大流。(不用管 \(S',T'\),因为跑 \(S,T\) 都不会用到)
注1:删边 \(\iff\) 遍历出边,把 \(T\rightarrow S\) 的 \(+\infty\) 边容量变成 \(0\),同理找到 \(S\rightarrow T\) 对应反边容量也变成 \(0\)。
注2:具体代码实现,删边可以直接调用 e[s],e[t]
的最后一个元素,因为 \(+\infty\) 边是最后加上去的。
※ 注3:也可以不删边直接在残余网络上求 \(S\rightarrow T\) 的最大流。因为 \(T\rightarrow S\) 的 \(+\infty\) 边的反边容量就刚好是第一轮最大流的流量。所以直接求 \(S\rightarrow T\) 最大流刚好会包含这条边。
【有源汇最小流】
也是残余网络删掉 \(T\rightarrow S\) 的边,但是跑 \(T\rightarrow S\) 的最大流,然后可行流减去这个最大流。
【费用流】
一般先保证最大流,再保证最小费用。
每条边每流一单位的流量,就要付一份这条边对应的钱。
先抛弃 Dinic,回归 FF 算法。
但是这次我们每次找增广路,用最短路算法找到一条费用最小的增广路,然后增广。
注意带费用的边的反边,费用就应该是负数了,因为反边代表的是反悔,反悔就要把费用吐出来。
复杂度:\(O(nmf)\),其中 \(f\) 等于图的最大流。
【正确性】
首先根据 FF 算法,只要是找增广路,就一定是对的。
但是是否每次找单个最小,就能得到总体最小?
是。(因为 FF 算法的反边是保证了一切反悔的可能性)
【有源汇上下界最小费用最大流】
注意建图反边费用是负数!
注:所有额外边的费用都是 \(0\)。
类比有源汇最大流,我们建了图后先跑一遍 \(S'\rightarrow T'\) 最小费用最大流,设这一次得到流量 \(f_1\),费用 \(c_1\)。
然后(不删边)再跑 \(S\rightarrow T\) 的最小费用最大流,设这一次得到流量 \(f_2\),费用 \(c_2\)。
则总共最大流量是 \(f_2\),最小费用是 \(c_1+c_2\)。注意这里的费用不能直接取 \(c_2\),因为这里的费用不能直接用 \(+\infty\) 的边做巧合。
【题目】
Telecowmunication 奶牛的电信
这题是割点,不是割边。那怎么做?
把每个点拆成入点和出点。入点向出点连一条容量为删除这个点代价的边。
然后可以通信的点之间,入点向出点连 \(+\infty\) 的边。
于是就可以最小割了。
蜥蜴
最少留下 = 总 - 最多离开。
也把每一个格子分成入点和出点,对于每个格子只能经过多少只蜥蜴,其入点就向出点连多少容量的边。
互相能跳跃的格子,连容量 \(+\infty\) 的边。此时最大流就是最多离开。
除此之外,怎么分配初始蜥蜴?我们可以建立超源点 \(S\),\(S\) 向所有有蜥蜴的格子连边。
同时建立超汇点 \(T\),所有能跳出边界的出点都向 \(T\) 连边。
这题里面,我们认为格子的限制只在格子内部处理,格子之间是可以无限跳跃的。同时:我们认为蜥蜴不是一开始就在格子上,而是从一个位置 \(S\) 跳到初始格子上。
士兵占领
这题王老师的 std 写错了,把行和列的结点建到一起去了。
解法:
把士兵的分配看作都从源点出发,流到每个行,然后从每个行流到每个列上面去。
对行的限制体现在 \(s\rightarrow row\) 上,对列的限制体现在 \(col\rightarrow t\) 上,对格子障碍物的限制体现在 \(row\rightarrow col\) 上。用上下界网络流做。
具体而言:设第 \(i\) 行有 \(x\) 个格子可以放,则 \(s\) 向行 \(i\) 代表结点连一条容量 \([l[i],x]\) 的边。设第 \(j\) 列有 \(x\) 个格子可以放,则列 \(i\) 代表结点向 \(t\) 连一条容量 \([c[i],x]\) 的边。
然后如果 \((i,j)\) 可以放士兵,则行 \(i\) 代表结点向列 \(j\) 代表结点连一条 \([0,1]\) 的边。
然后求有源汇上下界最小流。
圆桌问题
构造一个图:左边 \(n\) 个点,右边 \(m\) 个点,代表单位和桌子。
\(s\) 向左边每个点连边,容量 \(r_i\);右边每个点向 \(t\) 连边,容量 \(c_i\);左边每个点向右边每个点连边,容量 \(1\)。
跑最大流,看看是否等于总人数。
如何找方案:循环左边每个点,再循环到右边的边,看看容量是否为 \(0\)。
星际转移问题
之前 ZR 模拟做过类似的。
把地月看作 \(n+1,n+2\),洪水填充判断是否有解。
拆点,每个点拆成 \(t\) 个点表示在不同的时间到这个点的状态。
初始源点向地球连 \(+\infty\) 的边,表示允许任意多人到地球。求最短时间就是看第一个时间点,到达月球的人数达到 \(k\)。
枚举 \(i=1\sim +\infty\),然后枚举每艘太空船,找到它第 \(i\) 时刻所在位置,然后从 \(i\) 时刻位置向 \(i+1\) 时刻所在位置连容量 \(h_x\) 的边。\(i\) 时刻的月球向汇点连 \(+\infty\) 边。
加了新边之后跑最大流,把最大流加入目前的人数中,判断是否达到 \(k\) 人。如果是,就输出答案;不是,进入下一轮。
植物大战僵尸
神奇的建模。
先拓扑排序找出所有环和环的后继。这些一定是不会被吃的。
如果保护者指向被保护者,答案就是在剩下的点之间求最大权闭合子图。
(闭合子图:选一些点,使得如果 \(u\) 被选,则 \(u\) 的所有前驱都要选)
建模:建立超源超汇,原来保护关系的边都方向取反后变成 \(+\infty\) 的边。
然后,若点 \(u\) 权值为正,\(S\rightarrow u\),容量 \(a_u\);否则 \(u\rightarrow T\),容量 \(-a_u\)。
去掉拓扑排序后,所有收益为正的植物的收益之和 减去 最小割即答案。
拍照
也是最大权闭合子图。
对 \(m\) 个拍照事件做出 \(m\) 个点,\(n\) 个人做出 \(n\) 个点。
类比上面,每个人都保护了一些拍照事件。
海拔
观察:最终每个点的高度要么是 \(0\),要么是 \(1\)。且最后的花费就是所有 \(0\rightarrow 1\) 的边的人流量。
观察:最后的图中,\(0,1\) 各形成一个连通块。
所以我们的问题:分成两个连通块,使得联通它们的边边权和最小。
直接最小割,但是发现还要优化。
对偶图!
对偶图是一种平面图最小割的优化方法。可以把最小割转化为最短路。可以上网搜。
CF704D
思路和蜥蜴一样,每个点的颜色视作先从超级源点流到每个 \(x\) 坐标上,再从 \(x\) 坐标流到 \(y\) 坐标上。然后就变成上下界网络流。
点要离散化,但是不能用 map
,因为点可能重复.
餐巾计划问题
补充题意:初始没有餐巾;最后一天过了,餐巾是不是全部干净都行。
把每一天看成节点,餐巾的使用视作在节点之间流动。
具体而言,建立 \(2N\) 个点。每一天对应两个点,分别表示在这一天内干净的餐巾和脏的餐巾。
(以下没说费用都是 \(0\))
如何连边?首先,干净点 \(A\) 向干净点 \(A'\) 连 \(+\infty\) 的边,表示这天可以选择任意数量餐巾不用;脏点 \(A\) 向脏点 \(A'\) 连 \(+\infty\) 的边,表示这天可以选择任意数量餐巾不洗。
\(s\) 向 \(1\) 连边,容量 \(+\infty\),费用 \(p\),表示买餐巾。
\(i\) 向 \(i'\) 连边,容量 \([r[i],r[i]]\),表示第 \(i\) 天会产生 \(r[i]\) 个脏餐巾。
\(i'\) 向 \(i+m\) 连边,容量 \(+\infty\),费用 \(f\),表示快洗;
\(i'\) 向 \(i+n\) 连边,容量 \(+\infty\),费用 \(s\),表示慢洗。
\(n\) 和 \(n'\) 向 \(t\) 连边,容量 \(+\infty\)。
跑上下界最小费用流即可。
深海机器人问题
难点在于怎么处理每个生物标本只能被采摘一次。
首先生物标本是在点上的,对点的限制不好直接在图上体现。我们用一个经典 trick: 入点出点。
然后对于一个生物标本,我们从入点到出点连一条容量 \(1\) 的边,费用是生物标本的价值。
另外每个入点到出点,都连一条容量无穷,费用 \(0\) 的边。
跑最大费用最大流。
志愿者招募
我们把问题看作我们一开始有无限个志愿者。题目的“雇佣”就是让一些志愿者开始工作。设置 \(n+1\) 天,每一天抽象为一个结点,志愿者视作随着时间(天数)流动。
假如第 \(i\) 天需要 \(p_i\) 人,则从 \(i\) 天向第 \(i+1\) 天连一条容量为 \(inf-p_i\) 费用 \(0\) 的边,表示第 \(i\) 天最多有 \(inf-p_i\) 人休息。
一类志愿者可以从 \(s_i\) 做到 \(t_i\) 天,则从 \(s_i\) 向 \(t_i+1\) 天连容量 \(+\infty\) 的边,费用 \(c_i\)。
然后超源点 \(s\) 向第一天连容量 \(+\infty\) 费用 \(0\) 的边,第 \(n+1\) 天向超汇点 \(t\) 也连同样的边。
这么跑最小费用最大流:
首先人数有 \(inf\) 个,最终也必须剩下 \(inf\) 个人(总流量必须是 \(inf\))。为了最小费用,就会使用尽可能小的费用把 \(inf\) 个志愿者送到第 \(n+1\) 天,符合要求。