学网络流有一种初中平面几何的感觉,离开了模板就是群魔乱舞…
网络流怎么写就像地基之于大楼,只是一个基础,并不是网络流的重点,网络流的重点在于怎么建模。这一篇学习报告的目标就是通过一些做过的题目总结经验,进而总结一些网络流的基础元素和常见的建模套路(先挖个坑,更复杂的问题在下一篇)。
在进入正题之前,需要铺垫几个经典网络流模型。
最小割模型
所谓割就是一种点的划分方式;通俗的来说,就是一个边集,满足去掉这个边集后
S
S
S 和
T
T
T 不连通。去掉的这些边的容量之和就是这个割的容量,容量最小的割就是最小割。
最小割等于最大流。简单来说,最小割产生的包含
S
S
S 的集合出边都是满流而入边都是0,此时对应的情况也就是最大流。因为有这个性质,最小割可以用网络流求解。
最小割作为最大流的一个基础应用,适用的范围非常非常广泛。
最大权闭合子图问题
闭合子图就是一个原图的子点集,满足这个集合当中全部的点出边都指向这个集合内的点。一种常见的问题就是若干事件必须要以若干事件为前提发生,那么最大权闭合子集就对应着一个价值最大的方案。由于这个问题也是要求一个集合,那么这个问题也可以转最小割求解,只不过需要取一个补集。具体的下面会讨论。
常见建模元素
为什么是网络流?
根据上一部分可知,最常用的网络流算法Dinic(以及SSP)复杂度最差能达到
O
(
n
2
m
)
O(n^2m)
O(n2m) ,这个复杂度一比较差,二比较水,所以网络流算法需要有一些复杂度以外的判断方式。
思考一下,网络流能解决哪些以往的图论算法当中没出现过的问题?
首先最突出的特点就是网络流的流量,网络流能解决有上下界可选的问题。此外,网络流尤其是费用流的一大特点是单位权值,这是其他情况比较少见的。
这一类问题似乎动态规划照样可以胜任,但是动态规划只能求解无后效性的问题,网络流不在乎这一点,网络流求解的问题当中可以有复杂的元素间关系或状态;或者说如果元素间关系如果不够复杂没有必要用网络流。事实上,本质上网络流其实是一种经过反复尝试直接求出方案的算法,而不少动态规划当中输出方案就是一件很麻烦的事情。
另外,鉴于关系复杂这一点,有些问题状态类似但互相有影响,这时候如果使用动态规划维数会非常高,但是用网络流就没有这个问题(比如传纸条)。
事实上,有一些网络流从问题到数据范围会给人一种类似于动态规划的感觉,这个时候能否想到转化成网络流模型,就在于判断这些性质。至于怎么把这个问题变成一个网络流模型,那就是后面要讨论的东西了。
点和边的意义
大量做题之后很容易发现,网络流不管怎么变化,模型的意义怎么不同,都有一个共性:没有点权。事实上网络流并非不能有点权,而是由于网络流单位权值和可选的性质,大部分时候所谓的“点权”都不是点能维护的了的。
因此我们完全可以认为,点在网络流模型当中重在它的现实意义,便于思考怎么建模,事实上一切的信息除了编号全都是边权和容量,而不放在点上。
接下来就通过不同的情境,分析一下不同类型的问题当中怎么处理点的编号和边上的信息。
拆点和限流
T2 数字梯形问题(洛谷P4013)
这道题对路径有很多要求,不管哪一个动态规划都没法做,而网络流跑边集可以。
考虑把这三个问题互相对比来总结它们各自的特点。
第一问,路径互不相交,也就是每一个点只能通过一条路径,而第二问每一个点可以通过任意多条路径;前两问每一条边只能属于一条路径,而第三问每一条边还可以属于任意多条路径。
由于每一条路径的贡献都是1,对于后者,前两问只要把向下转移的边容量设为1就可以了,代表这个边只能算在一个路径中;对于前者,点上没法限制,那么把一个点变成两个相连的点,连接它们的边容量为1,这样一来就相当于这个点只能算在一个路径当中了。
解决第一问剩下的就容易了,第二问当中点之间的边容量改为
∞
\infty
∞ ,第三问除了初始进入顶端的
m
m
m 股水流容量为1剩下的全部是
∞
\infty
∞ 。要注意的是,虽然每一个路径只统计一次贡献,但是在后两问当中不同路径可能共用同一个终点,所以需要把指向汇点的容量也改成
∞
\infty
∞ 。
现实当中我们可以想到水流汇在一起又分散成若干股的样子,事实上,我们如果一一对应,会发现其实一个点就相当于无数并行的水流,所以干脆把一个点拉长变成一条边是可以想到的。
另外,此题是最大费用最大流,考虑到这个图是一个层次很明显的二分图,所以完全可以直接把边权取反最后输出相反数。
再另外,考虑到网络流求解过程中容量会改变,所以每一问必须都要把图重建一遍。
代码略。
综上,当点上存在通过限制时拆点。
拆点维护点的信息,这实际上体现了网络流当中边的限流作用,在有些情况下,由于我们希望通过网络流求一种方案,起初并不知道内部的具体分配,就可以通过限流控制流量的上限。对于刚才说的拆点,在很多情况下点权可以直接分到边上而不需要拆点(比如T2的第二第三问其实就不用拆点),这时候也体现了限流的作用。
分集合和求割
分集合和求割在逻辑上是互补的,这两者都是基于最小割。
在网络流问题当中,最理想的一种图就是二分图,在二分图上Dinic算法的时间复杂度会降低很多。很多题目当中点与点之间有一些关联,这些关联建成边之后就会自然使得点分为不同的集合,把不同集合分配到源汇点的一侧,就能变成最小割可以求解的问题。
有些时候集合的分法是摆在明面上的,有些时候则是需要看问题的特殊性质,比如黑白染色。
“组合选取”问题
所谓组合选取问题,指的是这样一类问题:A和B两个集合中有若干有权元素,如果同时选择了某两个元素就会产生额外的权,求一个总权值的最值。
首先是最简单的一种问题:A和B两个集合,同时把x元素放在A集合、把y元素放在B集合会产生一个额外的权,求最小权值和。
考虑对于有特殊关系的一对x和y,总的选取方案有三种:全选A,全选B,选一个A和一个B,这时还有额外权值。基于最小割,考虑能否用割边表示这一对选出的合法解,可以构造出这么一个模型:
为了在这一对当中求割,会发现合法的割恰好就是那三种方案。于是就可以求解了。在这个问题当中,边上的容量实际是边权,只是因为求最小割等价于求最大流,所以才把这个边权设成容量。所以在最小割问题中需要分清割=最大流和费用流的区别。
顺着这个问题继续思考,还有几种其他的情况没有考虑:
(1)x和y同时放在A/B集合有额外的权,其他不变;
(2)x和y同时放在A/B集合有额外的权,求最大权值和,其他不变;
(3)求最大权值和,其他不变。
对于(1),我们可以延续刚才的方法,只不过这一次需要改变一下边权。考虑一下刚才的四种割法(ax+ay,bx+by,ax+v+by,ay+v+bx),只需要重新代入解一下方程组就行了。
对于(2),我们还是延续一开始的方法,但是同时把x元素放在A集合、把y元素放在B集合产生的权改为看作减少的权,这时发现求的就是最小割割掉剩余的边(ax+ay+v,bx+by+c,ax+by,ay+bx),换句话说,取个补集就行了。
此外,应用补集还有一种很常见的方法:额外权是x和y划分的一种特殊状态产生的,既然如此,直接把这一特殊状态建成一个点,表示如果x和y满足这一状态就产生权值,如下:
总结(2)的经验,我们有两种建图的方法,还有补集思想可以一用,只要把产生的贡献看作扣掉的贡献就行了。
但是还是有一些例外,比如(3)。
由于这就是个最小变成最大,采取补集思想做一下。然而经过实践可以发现,我们没法再直接用这两个模型求解,因为这里不改变模型直接取补集是把最大最小和相同不同集合划分这两对约束全部取反,单独取反一对就不是单纯补集思想。这个在(1)就已经有所体现,只是对于(3)这样一个单纯的最小变最大我们更偏向于取个反这样简单的操作去求解,实际两者是一样的道理。
T3 圈地计划(洛谷P1935)
这道题就是(3)情况。采取第一种模型,不过这道题由于是相邻的全部都要产生约束,所以不必去解方程组,直接黑白染色((i+j)&1)
,把白格子上的点的A收益和B收益反过来,再取补集。这也就相当于最大最小的限制取反一次,不同相同集合划分的限制取反两次等于原限制。
代码略。
“选与不选”问题
这类问题就是上面提到过的最大权闭合子图问题的一种。某一个事件发生需要以其他若干事件发生为前提,每一个事件发生都有自己的花费/收益,求最大收益。
在求解这类问题的时候,把产生花费的放在汇点一端,产生收益(答案)的放在源点一端,各自与源汇点连边,容量即为收益/花费的绝对值。两个集合中间连依赖关系。
假设有如下图的这么一组:
考虑用最小割求最大集合,割无非就是两种,一种割val,一种割cost,全集设为val的和,这样一来,割掉val就表示不选这个点x产生的收益,割掉cost就表示选择x这个点产生的收益,同时支付选了三个y点的费用。这样就可以求解了。
这类问题的关键在于分集合,有负权(花费)的在汇点侧,有正权(收益)的在源点测。有一些情况下问题可能会更复杂,但是只要能转化出答案=收益-花费的形式就可以尝试这一种解法。
T4 Hard Life(UVA1389)
前排提醒:此题卡精度,而且卡精度卡的非常莫名其妙,至少我被卡了一个半小时都卡不过去。
首先这题已经把分数规划写脸上了。
假设当前二分的答案是
m
i
d
mid
mid ,那么该答案有解等价于:矛盾数 - 点数 * mid ≥ 0.
发现这个形式非常像上面提到的答案=收益-花费(这种表达式在网络流当中似乎不太常见),发现一对矛盾(x,y)相当于以选择x,y为前提的事件,于是按上面的套路建图,源点连矛盾,容量1;矛盾连点;点连汇点,容量mid。外面二分即可。
代码略。注意每一次检验是否有解都要重新建图。
匹配
前面讲到的“组合选取”其实就可以看作是一种匹配,几个元素匹配之后会产生权值。
匹配问题的关键就在于怎么分集合,毕竟这类问题不分集合是根本无从下手的。
有些问题明确的提出了对集合的要求,比如一本练习册可以配合上某些答案和某些教科书,问能配出几套学习资料(教辅的组成,洛谷P1231),显然答案和教科书压根就不是一个集合,当然是不能混为一谈的;有些则是类二分图的性质,每一个组合对应的元素都在不同的集合中。
T5 数字配对(洛谷P4068)
这道题显然可以暴力枚举两个数是否能配对来建边。一开始容易想到按分子分母分集合,但是很快发现这两个集合没法共享一个容量上限,换句话说,每一个数必须只能出现在一个集合里面。
所以需要考虑能匹配的两个点有什么性质。由于它们的商是一个质数,假设把这两个数质因数分解,这两个数包含的质因数次数正好差一。换句话说,它们的质因数次数奇偶性不同。于是求一下每一个数的质因数次数和,按奇偶分集合,暴力连边就可以了(注意判断一下点所属的集合,别把边给连反了)。
代码略。
很多只有两个点组合选择分集合的问题最后都是回到奇偶性上。
T6 水晶(洛谷P5458)
温馨提示,不要在Linux系统下做这道题,实测Linux系统虚拟机看不到洛谷上的图片。没有图这题真的没法想。
能量源的分布是很有特点的,通俗的来说,就是总是顺着角连出去的六条边的方向放一个。这样的一道题,一开始大部分人应该都会从匹配的角度去思考。
原题希望在去掉指定集合的前提下使得剩下的水晶权值尽量大,可以想到,这个所谓的去掉的过程就是割,现在只需要考虑怎么割,换句话说,就是怎么表示两种不合法的关系。
每一个组合里面有三个水晶,尽管第一种没有说,但第一种也是必然包括一个能量源的。考虑对这张图做一个三染色,就会变成这个样子:
发现红黄蓝三个颜色的六边形恰好是错开的(u1s1,这个模型应该背下来),而原题的两种不合法方案恰好就是三个不同色水晶相连的情况。
这样一来就好办了,不妨设构造方案为S-红-蓝(能量源)-黄-T,依据点上无权的思想,把三种点全部拆了,点内部连等于水晶价值的边。这道题还要注意,题干中强调了一个点可能放多个水晶,所以对于一个点上的水晶要进行手动去重。
此题还有一个问题:由于有x,y,z三维,每一维长度都是2000,如果用矩阵二维变一维的方法编序号存不下。首先可以无脑一个map,其次根据数学知识,一个平面上两个不共线的向量可以作为基底,所以可以直接用x,y两维表示z这一维的伸展,更具体地,
e
x
+
e
y
=
−
e
z
\pmb{e}_x+\pmb{e}_y=-\pmb{e}_z
eeex+eeey=−eeez ,消去z轴之后x,y变成了长度为4000的两维,就可以接着用哈希表示了。
称得上一道经典题。
这道题的建图过程有一些细节,不是特别好写,所以列一下主要的代码:
for(i = 1;i <= n;i++){
x[i] = read(),y[i] = read(),z[i] = read();
scanf("%lf",&w);
if((x[i] + y[i] + z[i]) % 3 == 0) w *= 1.1;
sum += w;
if(!val[id(x[i],y[i],z[i])]) val[id(x[i],y[i],z[i])] = w;
else val[id(x[i],y[i],z[i])] += w,flag[i] = 1;
if(!pos[id(x[i],y[i],z[i])]) pos[id(x[i],y[i],z[i])] = i;
}
for(i = 1;i <= n;i++){
u = (x[i] + y[i] + z[i]) % 3 + 3;//不要忘了+3
if(flag[i]) continue;
save(i,i + n,val[id(x[i],y[i],z[i])]);
if(u % 3 == 1) save(s,i,inf);
if(u % 3 == 2) save(n + i,t,inf);
if(u % 3 == 0){
for(j = 0;j < 6;j++){
p = x[i] + dx[j],q = y[i] + dy[j],r = z[i] + dz[j];
if(val[id(p,q,r)] > 0){
v = (p + q + r) % 3 + 3;
if(v % 3 == 1) save(n + pos[id(p,q,r)],i,inf);
if(v % 3 == 2) save(n + i,pos[id(p,q,r)],inf);
}
}
}
}
T7 交换棋子(洛谷P3159)
直接把黑白棋盘写脸上,这不直接黑白染色分集合?
从经典的华容道问题移动空格找目标开始,我们就已经知道,交换问题只需要考虑也只需要操作参与交换的一方。加上同色所以一上来直接把白棋和起终点重合的黑棋扔了,然后源点连接起始状态,结束状态连汇点。
每一个点都有交换次数上限,点上无权,拆点。
但是这个拆点是有讲究的,进入前面的点是进入这个点,出后面的点是离开这个点,而这个进出的过程发生了两次交换。所以这个拆出的边能经过的次数应该是点权的一半。还有一个问题:不一定棋移动过每一个点都是进出的,可能只出不进或者只进不出,这两种情况只会出现在这个点是某个棋的起点或终点的情况下,此时也就会出现奇数次交换。考虑到之前是向下取一半,因此如果某个点是起点或终点且能交换次数是奇数,就额外补上一次。
这道题的整个建模过程可以说是缝合了相当多黑白染色问题的元素。
代码略。
一点多意义
前天:我已经知道网络流的题怎么做了
今天:我会个*的网络流
一个点可能有很多个现实意义,更具体的来说,每一个不同的权值或信息都对应一个不同的意义。
这方面就体现出了点比边更灵活的地方,点上虽然无权,但是点的实际意义更丰富。对于一个具有多个意义的点,同样要采取拆点。要注意这种情况下的拆点是为了体现它的多种意义,而不是表示出点权,也就是说拆出的点之间可能没有边相连接,不能混为一谈。
T8 Binary Tree on Plane(CF277E)
这道题当中需要构造的是一棵二叉树,也就是每个点最多只能连接两个儿子。可以想到的是从源点给出2的流量,点给汇点1的流量,代表这个点在树上。费用流就可以求出是否有解(最大流为n-1)和最小的二叉树。
现在的问题就是怎么构图。由于每一个点都可能是父亲或儿子,所以一层点是不足以表示的。把每个点拆成两个,一个代表做儿子的点,一个代表做父亲的点,源点连父亲,儿子连汇点(这某种意义上也是一种分集合),如果x可以作为y的父亲,就把x的父亲点连向y的儿子点。这样就可以求解了。
在这一问题当中,让一个点前面当父亲后面当儿子难以传递和统计权值,而拆成两个不同分工的点就能让求解变得很清晰。
代码略。
T9 修车(洛谷P2053)
假设第
i
i
i 位修车师傅当前修了
j
j
j 辆车,那么这辆车的等待时间就是
T
1
,
i
+
T
2
,
i
+
.
.
.
+
T
j
,
i
T_{1,i}+T_{2,i}+...+T_{j,i}
T1,i+T2,i+...+Tj,i ,修前
j
j
j 辆车的总等待时间就是
j
×
T
1
,
i
+
(
j
−
1
)
×
T
2
,
i
+
.
.
.
+
1
×
T
j
,
i
j\times T_{1,i}+(j-1)\times T_{2,i}+...+1\times T_{j,i}
j×T1,i+(j−1)×T2,i+...+1×Tj,i 。由于单个人实际上修车的等待时间是一个前缀和,网络流的边权不能是一个可变的值,所以需要把这个时间重新考虑一下,相当于第
i
i
i 位师傅修队列里的倒数第
j
j
j 辆车会让这一队等待的时间增加
j
×
T
j
,
i
j\times T_{j,i}
j×Tj,i ,这一来就可以建成网络流模型了,可以想到是S-车-师傅-T的顺序。
很明显,如果把修车师傅看成点,没有办法处理修了多少辆车,而且由于同时只能修一辆车,所以不能直接每个师傅接若干条边。把修车师傅拆成若干个点,表示修了几辆车,对应上前面的边权。图上的边流量全都是1,因为车是唯一的,每一个时刻能修的车也只有1辆。
代码略。
以上两道题在拆点上逻辑是很清晰的,这是因为每一个点不同的意义很清楚。有些问题不同点的意义并不是题目当中给定的,而是为了求解建的,这些情况下就复杂一些。
总体上来说,不讨论具体的一个点有哪些意义(就像讨论为啥要把答案和教科书分成两个集合压根没有意义),考虑一下有什么信息是一个点没法解决的。
在这些问题当中为什么要拆点?想象一下,假设连在拆开的点上的若干边连在一个点上为什么不能求解。
在第一个问题当中,我们要统计的是流到子节点的信息,但是不清楚哪一个是子节点,所以必须全都连上。这时候如果不拆点相当于统计到了不应该统计的信息,因为网络流求解的过程中不会区分边。
在第二个问题当中,其实可以从一辆车建多条容量为1的入边到同一个师傅上,这并没有什么区别。问题在于,每一个师傅同一时间只能处理一辆车,可能有多个车选了同一个时间来修车,但是没有办法规定一个容量为x的出边里的流就是从x个时间点来的。假如只有一个时间点,可以拆点成边限制,但是多个时间点并不能这么做。
综上所述,我们发现之所以没法一点多用,主要是同一点:网络流问题当中不能区分出边,流汇到一点之后就混在一起了。具体到实际问题当中,就是汇点会统计到不应该经过的信息。因此为了解决这样的问题,关键还是要分清集合,哪一个集合连接源点,哪一个连接汇点,这样就能分清拆点的逻辑了。所以练习网络流的重点之一就是积累划分集合的经验。
T10 星际竞速(洛谷P2469)
这道题乍一看人畜无害,但是实际上对于当时(三天前)刚会拆点成边限流的我真是降维打击。
一上来就有一个问题摆在面前:怎么判断n个点全部走过了一遍?
考虑限流,每一个点向汇点连一条容量为1的边,全部流满的时候就走完了(这个技巧严格来讲应该放在底下满流费用流问题的时候)。然而这个时候就会遇到和前面T8一样的问题,我们既要选择走航路去其他点,也要往汇点流计算贡献。而且由于需要计算费用,不能随手开流量为
∞
\infty
∞ ,因此需要把一个点拆开,分成走航路的点和统计答案的点(后面简称前点和后点)。
对于直接花费
a
i
a_i
ai 到一个点的方式,由源点连到后点;对于走航路到一个点的方式,由前点连到后点。这一来统计到达哪些点的方式就正确了。
现在还有一个问题,到达的总是后点,怎么表示出发?可以由源点连到前点,费用是0,这就相当于出发。由于前点不连接汇点,所以走前点不算到达这个点而算离开这个点,因此统计是正确的。这道题也有一个启发,形象化来说都是从源点出发到达汇点,而在原图上走的逻辑本质是一个先到达后离开的过程,这个时候先后与源汇恰好是相反的。所以不能被惯性思维坑了。
代码略。
方案输出
由于网络流问题在实践当中通常是一种“可选”类的问题,加上实际意义是以点表示的,因此网络流的方案输出大多数时候是通过残量网络看选点的情况。
T11 太空飞行计划问题(洛谷P2762)
建图部分就是个裸的最大权闭合子图,跳过。
考虑一下怎么输出选点的方案,显然我们选的点就是那些上过增广路的点,也就是这些点的
d
e
p
≠
0
dep\neq 0
dep=0 ,就完事儿了。
T12 火星探险问题(洛谷P3356)
这道题更进一步,需要求路线,所以需要在残量网络上dfs.
先来看一下当时我写的dfs:
void dfs(int now,int fa,int id){
int i,temp;
if(ed) return;
if(now == t){
ed = 1;
return;
}
for(i = head[now];~i;i = e[i].nxt){
temp = e[i].to;
if(ed) return;
if(temp == fa) continue;//(2)
if(temp == now + n * m || now == s){//走的是拆点的边/S
dfs(temp,now,id);
}
else{
if(e[i].v == inf) continue;//(3)
if(temp == now - n * m + 1) printf("%d 1\n",id);
else if(temp != t) printf("%d 0\n",id);
++e[i].v;
dfs(temp,now,id);
}
}
}
这个dfs实现不够优秀,分析一下它的问题:
(1)首先这个if-else的写法就很垃圾,没有必要写两遍dfs还挂个else ,属于是忘了火车载客的痛苦回忆了 。
(2)RT,在走路线的时候往往是不走反向边的,因为没有现实意义,所以只要这个边的序号是个单数就不要去走了。
(3)RT,如果容量不相同判断就会很麻烦,所以应该判断这条边对应的反向边是否有流。
改进之后如下:
void dfs(int now,int id){
int i,temp;
if(ed) return;
if(now == t){
ed = 1;
return;
}
for(i = head[now];~i;i = e[i].nxt){
temp = e[i].to;
if(ed) return;
if(i & 1 || !e[i ^ 1].v) continue;
if(temp != now + n * m && now != s){
if(temp == now - n * m + 1) printf("%d 1\n",id);
else if(temp != t) printf("%d 0\n",id);
--e[i ^ 1].v;
}
dfs(temp,id);
}
}
T13 LIS(洛谷P3308)
这道题构造的部分是一个动态规划转网络流求具体方案的典型。从动态规划求LIS上考虑 (这地方就别想那堆优化的版本了) 。设原序列为
a
a
a ,
f
i
f_i
fi 表示以
i
i
i 结尾的LIS长度,点
i
i
i 能够转移到点
j
j
j ,一定有
a
i
<
a
j
∧
f
i
+
1
=
f
j
a_i<a_j \land f_i+1=f_j
ai<aj∧fi+1=fj ,此外
f
i
=
1
f_i=1
fi=1 接
S
S
S ,
f
i
=
a
n
s
f_i=ans
fi=ans 接
T
T
T ,想要使LIS变短,就得把所有能够转移出当前长度的LIS全部切断,点上无权,拆点成边,求最小割就完成了第一问。
第二问和第三问实质上都是求割集,而且是带权的。
割集是一些特定的边的集合,所以不是单纯的一次dfs能解决的。
一条边在最小割上,有以下两个条件:
(1)一定是满流的。这是基于最大流的性质。也可以这么考虑,由于最小割是拿权值当容量,所以只有满流边能代表原权值,不选满流边最小割无意义。
(2)对于这条边相连的两点,不存在其他的增广路。这还是最大流的性质,有增广路的话这条边就不是在增广路上的。
由于要升序,考虑按这个特征值顺序枚举每一条边
(
u
,
v
)
(u,v)
(u,v) ,按以上的要求判断。
值得注意的是,可能存在很多条边和这个割边断开的路径集合相同,所以判完之后直接清空
S
−
u
,
v
−
T
S-u,v-T
S−u,v−T 的路径上的全部流量(实现方式就是反向跑Dinic),或者干脆把这条边删了重新跑最小割。
为了便于找到边的序号,先存这些拆点产生的边。
代码如下:
for(i = 1;i <= n;i++){
x = c[i].id;
if(!bfs(x,x + n)){
buc[++tot] = x;
e[x * 2 - 2].v = e[x * 2 - 1].v = 0;
for(j = 0;j <= ecnt;j += 2){
e[j].v += e[j ^ 1].v,e[j ^ 1].v = 0;
}
Maxflow(s,t);
}
}
满流下费用流问题
费用流最常见的情况就是费用最大流。很多时候,流量是一个硬性限制或者建图的必要工具,在此基础上求最大/最小权值,此时的流量就是满流,求满流下的费用。在这类问题当中,主要问题就在于怎么让满流表示出限制。
例如前面的T10,每个点必须全部恰好走一次的硬性限制就表示为边的流量全部为1,当汇点接受的流量是n的时候就达成条件了。
T14 餐巾计划问题(洛谷P1251)
这道题要求每一天至少要提供
r
i
r_i
ri 块餐巾,显然今天用不上的餐巾可以留着第二天用,因此这个“至少”就可以忽略了。假设每一天连向汇点,那么汇点收到的总流量是
Σ
r
i
\Sigma \,r_i
Σri 的时候就完成了,这就是卡到满流。
接下来考虑怎么处理餐巾。按照刚才的思路,每天需要提供餐巾,而这些餐巾用完之后需要处理,由于提供餐巾连向汇点,就没法把用完处理的过程连在这些点上,于是拆点,一个表示提供餐巾,一个表示处理餐巾,源点向处理餐巾连
r
i
r_i
ri 容量的边。两种洗餐巾都是从处理点连向若干天后的提供点,购买餐巾直接从源点连向提供点(和前面相同,源点同时具有多个意义)。至于把多余的餐巾留下,不管是否处理好都可以留着,所以两个集合的点各自顺次连接。
总的来说这道题和前面的T10特点很相似,不过各种特点体现更直接,而且逻辑上更反认知一些。
T15 志愿者招募(洛谷P3980)
这道题要求每一天至少要有
a
i
a_i
ai 人,我们尝试类比前面的思路来求解。但是很快会发现一个问题,假设我们采取S-人-天-T的顺序建边,由于招募的人工作周期是一个区间,所以没办法一条一条边往这个区间的每一个点建边。想到先汇总然后分到这些区间,不过由于网络流出边的任意性,仍然是错的。所以类比并不合适。
现在换个角度考虑一下这个区间覆盖,会想到可以借鉴差分的思路,从起点出加水,让水一直留到终点处结束这个计算的过程。我们没法在网络当中抽水,这里就有了一种特殊的处理方法:建一条从起点到终点的下一个点的边,在这里加流量直接补到终点处,加了多少流量,等同于跳过了这个区间内的多少需求的流量。
由此考虑建边的方法,源点连第一天,汇点连第(n+1)天,天数顺次连接在一起。招募的边已经建好了,现在考虑怎么处理这个每一天的人数限制
a
i
a_i
ai 。
现在的问题就是满流,怎么填充满流?
这道题最终希望的就是能够把每一条边全都填满,尽管表现招募的边是离开这一串天数的,但是实际上考虑的时候实际应该表现为加在一起是满流的(或者超出)。这相当于每一天的人数是一个下界,不妨从上下界网络流角度考虑一下,设这些边的容量为
∞
−
a
i
\infty-a_i
∞−ai ,那么不存在招募边的情况下这一段能够自然填上的流量就是
∞
−
m
a
x
{
a
i
}
\infty - max\{a_i\}
∞−max{ai} ,我们直接设满流为
∞
\infty
∞ (在这一情况下超出也是
∞
\infty
∞ ),那么这一段需要用招募填充的就是这个
m
a
x
{
a
i
}
max\{a_i\}
max{ai},符合要求。跑一个费用流就可以了。代码略。
总而言之这道题确实非常不好想,区间覆盖的操作本身就是一种比较少见的操作,加上这个在容量上取补集的操作,算是一种满流费用流下的特殊模型。
总而言之,网络流的一切模型总是从分辨点和边的作用、分集合开始的,之后再配合其他的一些思维,这一篇讲的也都是些基础元素的组合。但这个据题意分割集合的过程本身就不简单,总归是多学多练多思考。
下一篇会补完一些遇到的比较特别的网络流模型。