模拟费用流与反悔贪心小记
0. 一些约定
若无特殊说明,我们约定:
- \((u,v,c,w)\) 代表一条 \(u\) 到 \(v\),容量为 \(c\),费用为 \(w\) 的边。\((c,w)\) 代表容量为 \(c\),费用为 \(w\) 的边。
- \(S\) 代表源点,\(T\) 代表汇点。
1. 概括
模拟费用流与反悔贪心有着非常相似的思想,有些时候可以互相代替。无论是哪种解法,都能通过反悔决策增量地维护答案,因此有时可以解决动态规划或朴素贪心难以解决的问题。
2. 性质与基本思路
-
费用流模型关于流量具有凸性,可以通过费用流模型证明 wqs 二分的可行性。
-
在一些简单费用流模型里,不妨观察增广路和负环形式,简化费用流计算。时刻注意,在正常的费用流中,增广路是不会有环的。
-
在部分费用流模型里,可以将费用流退流决策一起放入计算中。
-
在模拟 EK 算法时,如果数据允许,可以尝试增量加边。
-
反悔贪心的反悔决策如果不能用增量比较,尝试用朴素的贪心策略进行反悔。
-
反悔贪心可以用双向链表,优先队列维护差值等方式进行连续反悔。
-
在反悔决策不明朗时,尝试将反悔也当成可行决策。
-
反悔时的增量可以看情况选择,有时不一定是一个一个增加的。
3. 模拟费用流例题
- 类型一:费用流模型证明 wqs 二分可行性。
发现这个问题如果直接反悔贪心有三种决策,分别是组一对新的 \(AB\),替换一个 \(B\),或者替换一对 \(AB\) 。在 \(k\) 的限制下直接做是不容易的。
费用流建模是容易的。
有了建模之后就有凸性了,考虑给 \(a_i\) 带权二分,解除了 \(k\) 的限制后剩下部分的反悔毙掉了替换一对 \(AB\) 这个操作,剩下两个操作可以直接反悔。
解除限制之后的 dp 非常典,现在考虑如何建模。
将每个坑的间隔看成一个点,每个坑成一条边,有公共坑的间隔连一条权为坑的权的边,边容量都是 \(1\),分成二分图即可。
- 类型二:简化费用流计算。
uoj 差评榜 rk3(好像是),鉴定为蒸。
这个题可以用反悔贪心解释:
我们给两个堆,堆 \(A\) 表示拆出一个送餐员的代价,堆 \(B\) 表示多一个餐厅的代价。从小到大加入,保证堆中决策坐标都比当前小。
当加入一个餐厅时:
- 寻找一个未匹配的送餐员,抑或是寻找一个可以替换的已在匹配中的餐厅。从堆 \(A\) 中找,假设代价是 \(W\),则贡献是 \(y_i+w_i+W\)。如果撤销该决策,多出这个餐厅,代价为 \(-2y_i-W\),加入到 \(B\) 中,再将替换这个餐厅的贡献 \(-y_i-w_i\) 加入堆 \(A\)。
- 否则将 \(-y_i+w_i\) 加入 \(B\) 中。
当加入一个送餐员时:
- 寻找一个可以匹配的餐厅,抑或寻找一个可以替换的已在匹配中的送餐员。从 \(B\) 中找,假设代价是 \(W\),则贡献是 \(x_i+W\)。如果撤销此决策,多出这个送餐员,代价为 \(-2x_i-W\),加入到堆 \(A\) 中。
- 否则将 \(-x_i\) 加入到 \(A\) 中。
这里有一个细节,那就是为了让它满流,我们提前往 \(B\) 中增加代价为 \(+\infty\),可用次数也为 \(+ \infty\) 的一个餐厅即可。
这里告诉我们反悔贪心的另一个套路,那就是将反悔与普通决策同质化。但是这里我要引出一个很厉害的费用流模型来解释这个反悔贪心。
先上图:
这张图的源点是 \(0\),汇点是 \(7\)。每个坐标拆成 \(in\) 和 \(out\),\(in\) 向 \(out\) 连一条费用 \(-2x\) 的边。\(in\) 从大往小,\(out\) 从小往大连 \(0\) 边,若有送餐员,从 \(S\) 向对应的 \(in\) 连费用为 \(0\),容量为 \(1\) 的边,若有餐厅,从对应的 \(out\) 连费用为 \(w_i\) ,容量为 \(c_i\) 的边。可以发现,这张图求解的最小费用最大流即为原问题的解。
现在考虑模拟 EK 算法,先单路增广 \(0 \rightarrow 3 \rightarrow 6 \rightarrow 7\) 这条路径,变成:
代价即为 \(y-x+w_i\),设 \(W=y+w_i\),此时形成一个环,假如这个环是负的,消圈后变成:
整体上,你加上了\(y_j+w_j-W\) 的代价,多了一条可能的 \(1 \rightarrow 4 \rightarrow 5 \rightarrow 6 \rightarrow 7\) 的代价为 \(-2y_j+W\) 的增广路,跟上次一样的,以后可能反悔这条 \(w_j+y_j\) 的边。这神似我们之前的反悔贪心,其实本质是一样的,分析另一种情况应该也能得出相同结论。
为什么要多讲一个费用流模型,因为接下来还有用这个模型解释的题(在反悔贪心那儿)。
Olympiad in Programming and Sports
这题数据范围其实可以直接费用流解,但是模拟费用流也是可以的。
本题的费用流模型这么建:建一个二分图,下部 \(n\) 个点,上部 \(2\) 个点,容量与费用显然。考虑模拟 EK 算法,每次寻找一条增广路,这条增广路的形式只有四种(以下用 \(A,B\) 来描述上部两点):
- \(S \rightarrow A \rightarrow i \rightarrow T\)
- \(S \rightarrow B \rightarrow i \rightarrow T\)
- \(S \rightarrow A \rightarrow i \rightarrow B \rightarrow j \rightarrow T\)
- \(S \rightarrow B \rightarrow i \rightarrow A \rightarrow j \rightarrow T\)
之外的增广路都是不优的(大概画一画,发现因为只有两个点,其他的都会形成负环,不可能出现)。
与 \(S,T\) 之间的连边费用均为 \(0\) 直接忽略掉,拿四个堆维护即可。
另一种很有启示的思路:考虑一条一条加入 \(S \rightarrow A,B\) 的边,我们先将 \(S \rightarrow A\) 的边全部加完。我们将最优的几个 \(a_i\) 匹配,一定是加完 \(S \rightarrow A\) 边后的最佳匹配。
之后我们继续加入 \(S \rightarrow B\) 的边,那么增广路有两种可能:
- \(S \rightarrow B \rightarrow i \rightarrow T\)
- \(S \rightarrow B \rightarrow i \rightarrow A \rightarrow j \rightarrow T\)
剩下的增广都不经过新加的 \(S \rightarrow B\) 的边,不考虑,这样我们就只用三个堆了。
这也告诉我们,在一些模型里,尝试提前增广一类型的边可以简化问题。下一道题就是这样。
上一道题能不能也这样做?回去再想想。
蔬菜越来越多,我该怎么办?
嘴巴一下:
给每天建个点,往 \(T\) 连 \((m,0)\) 的边,每个蔬菜也建点,这一步建图是简单的。
考虑倒推,每天加入当前的 \(m\) 条边,这个费用流性质很好,是个可以直接用堆玩的贪心。
好的你现在会 \(q(n+mp)\log{n}\) 复杂度了,我们考虑做多测,其实就是要从最后一天开始退流。发现对于最后一条边,你啥都能退,直接拿小根堆把所有东西都扔进去,做完了。
有点嘴巴,详细说说。
考虑先建模,每天建一个点,往 \(T\) 连 \((m,0)\) 边,第 \(i+1\) 天往第 \(i\) 天连 \((+\infty,0)\) 边,对于每个蔬菜,我们也建一个点(其实可以不建),从 \(S\) 连一条 \((1,s_i+a_i)\),连一条 \((c_i-1,a_i)\) ,从蔬菜刚好流失的那天开始,往每天连容量为当天损失蔬菜数量的边。
直接跑就是对的。如果我们做单测的话可以直接从那天开始,一次一天一天往前走,每天增广当前的 \(m\) 条边,容易发现增广路形式只有一种,直接用堆存可行的增广路即可。
如果多测的话,我们先用跑出 \(p = 10^5\) 的答案,那么我们就知道了我们要选哪些蔬菜。如果要往前推,要把当天的流退完,相当于找 \(T\) 到 \(S\) 的增广路。同样是只有一种增广路,唯一的区别在于,你不用在中途加入新的增广路,所有增广路一开始就已经可以流了。同样用堆存就行了。
如果你在考场上一眼瞪出可以费用流建模,就已经有二分套二分的 \(64\) 的高分了。然而想拿满,光靠这个还不够。具体考虑如何建模。
建模如下:建一张二分图,左右部分别当 \(a,b\) 连,要求 \(L\) 个相同的位置,相当于自由选择不同的位置至多 \(K-L\) 个。建两个虚点,中间连一容量 \(K-L\) 的边,两个点再分别向 \(a,b\) 连边即可。对于剩下的,我们限制他们必须选相同位置,\(a,b\) 想通过位置连边即可。流量固定为 \(K\)。
仍然考虑优先增广较为特殊的边,也就是那两个虚点中间的边。很明显,直接找到 \(a\) 和 \(b\) 中最大那几个就是最优。
继续增广,现在只能增广 \(a,b\) 对应位置相连的边了。一下我们称 \(i\) 为 \(a_i\) 对应的点,\(i'\) 为 \(b_i\) 对应的点,\(P\)、\(Q\) 对应两个虚点。
增广路有四种:
- \(S \rightarrow i \rightarrow i' \rightarrow T\)
- \(S \rightarrow i \rightarrow P \rightarrow j \rightarrow j' \rightarrow T\)
- \(S \rightarrow i \rightarrow i' \rightarrow Q \rightarrow j \rightarrow T\)
- \(S \rightarrow i \rightarrow P \rightarrow j \rightarrow j' \rightarrow Q \rightarrow k \rightarrow T\)
拿 \(5\) 个或 \(6\) 个堆维护这玩意就行了,其余的增广路都带个环。
在重做这道题的过程中,得到了一点经验,那就是在在手动寻找增广路径比较困难时,一定要记得使用小数据对拍 + 手玩,往往可以拍出漏掉的增广路。还是那句话,让程序帮你做麻烦的事情。
4. 反悔贪心例题
- 类型一:尝试使用普通贪心策略反悔。
如果只有 \(L\) 或 \(R\) 的限制,可以直接排序后贪心。如果限制同时出现,我们考虑反悔,在满足一边取到最优的情况下使另一边最优。
将 \(L\) 一维排序,然后从小到大做第一步贪心。此时我们保证了新加入的人一定可以替换掉之前的人,而如果当前的人能换出一个更小的 \(R\) 就换。
剩下没配上的人和位置再按 \(R\) 贪心。
听上去挺嘴巴的,但是确实没问题。
- 类型二:将反悔决策同质化。
雪灾与外卖的严格加强版,把数轴改成了树。
这题用网络流模型不好分析,但是也不是不行,先考虑反悔贪心的想法。
拆贡献成 \(dis_a+dis_b-2*dis_{lca}\)。
考虑雪灾与外卖的类似分析,两个堆 \(A,B\) 分别表示多出一个军队的代价和多出一个驻军地点的代价。
在每个节点上往 \(A\) 加入 \(X_u\) 个 \(dis_a\) ,\(Y_u\) 个 \(dis_b\),之后考虑每次从 \(A,B\) 中拿出一个军队和驻军地点,设贡献分别为 \(W1,W2\)。
那么如果连接他们的贡献为 \(W1+W2-2dis_u\),如果要反悔其中的军队,贡献为 \(2dis-W1\),多一个驻军地点,加入 \(B\),如果反悔其中的驻军地点,贡献为 \(2dis-W2\),加入 \(A\)。
直接拿配对堆暴力向上合并就行了,注意回收内存。
如果要用模拟费用流解释的话,大致思路就是,仿照雪灾与外卖拆点,\(in\) 向祖先 \(in\) 连边,\(out\) 向后代 \(out\) 连边,然后分析费用流负环情况,发现反悔后产生的反悔路径如果要形成新的负环,一定从当前节点也就是 \(lca\) 处往上或往下,否则不是最优的。然后就像雪灾与外卖一样加入新的反悔决策即可。
如果这题不是树而是 DAG 的话,能有同样的优化方法吗?其实在原来的建模越来越接近一般图的情况下,模拟费用流越接近普通的费用流算法。所以在反悔贪心时,也可以用费用流建模考虑反悔贪心的可行性。
upd:貌似 DAG 的话跟最小链覆盖差不多,那应该寄了(

浙公网安备 33010602011771号