反悔贪心杂题
反悔贪心杂题
笔者水平有限,若有笔误望指出。
笔者认为,反悔贪心其实质仅仅只是一类贪心策略,所谓反悔,仅为一个思维过程,体现为:拿出一个错误的贪心策略(很多时候体现为略去某些限制)并尝试使用更多的策略去修正它
反悔贪心有一个很常见的应用场景:从 \(i\) 的答案修正到 \(i+1\) 的答案,并且往往这个答案具备某种凸性(可能全局,可能是仅对 \(\pmod x\) 的点有凸性)
同时如果可以设计dp,这个 dp 过程往往也具备凸性(Slope trick?)
关于Slope Trick,斜率优化,反悔贪心,费用流(关于流量的最小费用函数是凸的),闵可夫斯基和, WQS 二分 是互相联系的。
反悔贪心可以从模拟费用流的角度解释:将原题建立费用流模型。
下面的叙述中会偏重“修正”思想。
BZOJ1577
一个贪心策略是能上车就上车,这样显然是错的,所以我们走到一个地方后,需要考虑当车塞满之后,撤销哪些牛让本站牛上车更优秀
可以发现,对于车上的牛而言,保留目的地更近的牛必然更优,所以踢掉原本车上的牛(目的地 \(d_1\))加入新的牛(目的地 \(d_2\)),如果 \(d_2<d_1\) 就是更优秀的,可以用一个优先队列维护这个返回过程。
数据备份
极为经典
给定 \(n\) 个数,现要求选出 \(k\) 个数字,其不相邻,且权值和最小。
显然会存在一个 \(O(n^2)\) 的 dp,这里略去。
我们考虑当前已经选出了 \(i\) 个数字,现在要选出 \(i+1\) 个数字,应当怎么决策?
-
选出当前可选的最小数字,并将其两侧标记为不可选
-
撤销掉某次选择,并选上其相邻的两个元素
如果只选择其中一个,那么不撤掉一定更优(对另外的点限制更弱且自身更小)
所以考虑维护这个撤销步骤,朴素的想法是维护两个堆,一个已经操作的堆,一个尚未操作的堆,取最优决策出来贪心。
但是这样是有问题的,因为撤销状态内部是有联系的,譬如你选了 \(x-1,x+1\),在处理撤销时两者同时选上 \(x\) 就是错误的,会有一个复杂的连锁反应。
但是注意到这个连锁反应是成段的,也就是设我当前选了 \(x,x+2,x+4…x+2m\),但是没有选 \(x-2,x+2m+2\),那么我撤销其中任意一个,都会导致当前的方案变为 \(x-1,x+1,x+3\dots x+2m-1,x+2m+1\)(选的个数恰好多一)
所以不妨将这样的整段限制缩为一个点,这个点的权值就设为两个方案之间的差值。
并且幸运的是,这个权值是容易计算的。
详细的说,最初,我们 \(n\) 个数单独成段,当我们选择第 \(i\) 段后,将 \(i-1,i+1\) 两个段吸纳进来(作为决策),同时删去这两个段并重新编号。
定义一个段的权值是变换操作后的代价与原本代价差,则 \(w'=w_{pre}+w_{suf}-w\)。
正确性是显然的,每次我们选择一个段进行操作,都用了最少的代价,且使得选择个数恰好增加一。
所以得到了一个可以使用链表维护段关系的简洁做法。
事实上,这样的反悔贪心,往往在堆中的某个点其已经不代表点了,而是一整段的决策
CF436E
我们仍然考虑从 \(i\) 的答案如何推究到 \(i+1\) 的答案,有如下决策:
- 将一个零星变为一星
- 将一个一星改为二星
- 将一个二星改为一星,同时让一个零星成为二星
- 将一个一星改为零星,同时让一个零星成为二星。
改动量显然不会超过两个点,因为改动更多点都可以被以上四个决策平替掉。
让我们看看这需要如何维护:
- 维护零星里最小的 \(a_i\)
- 维护一星里最小的 \(b_i-a_i\)
- 稍显复杂,代价是 \(b_i+(a_j-b_j)\),也即维护零星的最小的 \(b_i\),二星的最小的 \(a_j-b_j\)
- 类上,代价是 \(-a_i+b_j\),维护一星的最小 \(-a_i\),零星的最小的 \(b_j\)
也即我们需要 5 个优先队列维护决策,取最优的扩展即可。
CF280D
这是重要模型,是一个绝妙的想法。
考虑全局问题,我们每次取出最大子段和,然后将这一段取反(乘上 \(-1\)),接着下一次取的时候,如果包含了被取反的段,那么就相当于扔掉这一部分(删掉有交的部分),也就是反悔了。
其实是一个反悔机制,新增一段是什么样的:
- 选出一段不交的区间
- 删去原本至多一个区间的一个子区间,并将分裂的两个区间选出一个进行拓展。
而在这个操作里,根据最优性,如果这一次连续删除了两个之前选出的区间,那么上一次选这个必然更优,不可能发生,所以只会删一个。所以我们这样做是对的(只会增加最多一段区间)
所以只需要维护最大子段和以及取到最大子段和的区间,支持乘上 \(-1\),实现稍有难度
一个绝妙的实现:我们显然需要维护 \(lx,rx,mx,s,lrx,rlx,ql,qr\),分别表示以 \(l\) 为起点的最大子段和,其右端点是 \(lrx\),\(rx,rlx\) 同理,\(ql,qr\) 维护取到最大子段和的区间。可以照常更新。
但是我们可以维护两个这样的量(使用结构体),提前将乘上 \(-1\) 的部分预计算,那么每次取反相当于交换这两个量,可以避免维护最小值这种东西
在本题可以这样写,然后暴力执行 \(k\) 次即可通过,记得撤销。
NOIP模拟赛5.绳网委托
题目没了,我凭记忆描述一下:
给定 \(01\) 序列,定义一次操作为任选 \([l,r]\) 为将区间 \(l,r\) 翻转,对于 \(k\in [0,n]\),求出操作 \(k\) 次后的最长不降子序列。
考虑最长不降子序列:
可以赋权,将 \(0,1\) 分别赋值为 \(1,-1\),问题就变成了查最大前缀。
这也很经典
考虑这类翻转操作,翻转 \(k\) 次后,某个前缀和是如何组成的呢?
答案是,原本的某个前缀,加上 \(k\) 个原序列的不交子区间。这个可以自己手玩得到。
那么与上一题的区别是,我们 \(k=0\) 时,取出最大前缀,将其取反,接下来和上一题就一样了。
CF335F
335e已经够恶心了
题意其实是给定序列 \(a\),然后要求将其分为两个部分 \(S,T\),使得:
- \(|S|\ge |T|\)
- 将 \(x\in S,y\in T,a_x>a_y\) 连边,存在完备匹配
- \(\sum_{x\in S} a_x\) 最小
假定没有重复元素,可以想到将饼子按照 \(a\) 从大到小进行排序,设 \(f_{i,j}\) 表示前 \(i\) 个饼子中,还有 \(j\) 个饼子没有附赠品的最小代价
\(f_{i,j}=\min(f_{i-1,j-1}+a_i,f_{i-1,j+1})\)
可以 \(O(n^2)\) 而且感觉这东西有凸性。
如果有重复元素怎么办呢?
那就不妨设 \(f_{i,j}\) 为前 \(i\) 类 饼子,还有 \(j\) 个饼子没有附赠品的最小代价
那么有:
仍然可以 \(O(n^2)\) 做。
好了现在我们给出了一个 \(O(n^2)\) 的方法,优化路径只有两条:
- Slope Trick
- 贪心
好像有凸性,只看 \(j\equiv n\pmod 2\),但是斜率变化量过大,难以使用 Slope Trick。
回到最开始的条件,我们假设不存在完备匹配,应当可以调整
例如如果有 \(x\in S,y\in T,a_x>a_y\) 那么交换 \(x,y\) 之后如果存在完备匹配,交换后不会变差,同时若不存在完备匹配,肯定是用 \(T\) 的失配值移动过去,甚至可能考虑再换一个过来
考虑我们将一类饼干新加入决策集合(权值递减),决策其实并不多:
- 直接给钱
- 让还没指定白嫖对象的来白嫖
- 撤掉之前某个白嫖的,连着白嫖两个
里面最优秀的显然还是能够白嫖就白嫖。
我们考虑维护一个小根堆,里面维护 当前所有白嫖决策的花费,那么当前还能够白嫖的元素是可以计算的:总个数减去两倍的白嫖次数。
当考虑新加入 \(c\) 个 \(a\) 时,我们先把可以白嫖的存下来(由于权值严格偏序,所以不能加入)
然后考虑剩下的不能够白嫖的。
设 \(v\) 是堆顶,有如下方案:
-
\(v\le x\)(这是可能发生的,会有复杂的白嫖方案)
将其替换为白嫖 \(x\) 是不劣的,买了它后还能多嫖一个,所以还有新的白嫖方案是舍弃 \(v\),嫖两个 \(x\)
也就是两个白嫖方案。
-
\(v>x\)
此时考虑 \(w=2x-v\),这是买 \(v\) 白嫖两个 \(x\) 的收益,我们当前面临选择哪个决策更好,我们可以当作白嫖了物品 \(2x-v\) ,因为它对于后续的影响等价于一个物品(如果 \(w>0\) 的话) 和物品 \(v\) 因为它后续影响是一样的(你可以看作两个方案之间转化的代价)
所以有方案 \(v\) 和 \(2x-v\) 两个。
我们删去原本的方案,加入这两个新的白嫖方案即可。
Raper
这题有难度啊。
\(k\) 的限制其实并不容易处理,但是注意到反悔贪心与WQS二分的联系,感性理解下 \(k\) 的答案应当是有凸性的(你显然需要更多的光盘,那么代价会涨幅得更高(取出最劣的配对方案扔掉))
那么考虑 WQS 二分,问题就变成了每次配对有一定代价,求最小代价。
考虑当前的光盘可以分为几类:
- 没有操作的
- 半成品
- 成品
而我们显然能够变成半成品都变,这不会劣,那么每一时刻只会多一个初始权值 \(A_i\) 的半成品,和一次变成成品的机会 \(B_i\)
有如下决策:
- 将半成品变成成品:\(A_j+B_i-k\)
- 将一个成品的后半工序变成 \(B_i\):\(B_i-B_j\)
- 啥也不干
用两个堆,第一个堆维护成品的 \(A\),第二个堆维护成品的 \(B\)。
每次从三类决策里找到一个更新即可。
校内模拟赛题:楼房搭建
给定序列 \(h\),你最初有一个全零的序列 \(a\),每次可以进行两次操作之一:
- 选择 \(i\),\(a_i\leftarrow a_i+1,a_{i+1}\leftarrow a_{i+1}+2\)
- 选择 \(i\),\(a_i\leftarrow a_i+2,a_{i+1}\leftarrow a_{i+1}+1\)
求使得 \(\forall i,a_i\ge h_i\) 的最小操作次数。
一个很蠢的想法是每次不足就放 \((2,1)\)个,答案显然是 \(\frac{\sum a}{3}\)。
但可能会在后面造成极大的浪费(中间极高,两侧较小),那么在这个较高的位置时,我们将之前的 \((i-1,2,1)\) 替换为两个 \((i-1,1,2)\) 就好了,这使得二楼升高 \(3\)。
但是这样又会导致给到第三楼 \(i+1\) 的操作空间可能很小(第三楼也很高的话)。
发现第三楼可以在不改变第二楼高度的前提下自己拔高 \(3/6\),是三的倍数,那对答案就不会有影响。
\((i-1,2,1)\) 可以替换为两个 \((i-1,1,2)\),两个 \((i-1,1,2)\) 撤掉换成 \((i-1,2,1)\) 后二楼降低 \(3\),一楼不变,可以使用 \(3(i,1,2)\) 让二楼调整回来,三楼增高 \(6\),也可以被替换为 \((i,1,2)+(i,2,1)\),三楼增高 \(3\)。
也就是说,在当前楼尽量贪,而如果在后面发现不合适(矮了),也可以在不改变上一层和上上一层的条件下只改动这一层(按 \(3\) 的倍数),这就足够了。
可以存下当前可以执行的 \(+3\) 操作总量,以及当前操作下后一栋楼的基础高度,就可以计算出还需要多少次 \((2,1)\),以及可能的 \((1,2)\) 进行操作。
如果当前操作下后一栋楼的基础高度就已经满足要求了,就可以开始新的一段了(\(+3\) 操作和基础高度全部清空)
Jewels
考虑 \(i\to i+1\) 答案的变化,仅有如下决策:
- 给已经拿过的再拿一个
- 新开一个 \(2\),删掉一个之前的 \(1\)
- 连拿三个新的,删掉之前一个 \(2\)
考虑到每一类必然是递减拿,且最少拿两个,所以给最大值和次大值取平均是不影响答案的。
使用三个优先队列 \(q1,q2,q3\),
- \(q1\) 维护所有的物品
- \(q2\) 维护所有的未拿的连续三个
- \(q3\) 维护已经拿了的可以删的元素
每次有:
- 若 \(q1\) 堆顶的元素从未取出过,说明这个无法参与,那么有如下决策:
- 连拿三个新的,删掉之前的一个 \(2\)
- 拿一个已经拿过的
- 扔掉之前的一个 \(1\),连拿两个堆顶元素(至少两个)
- 此刻,注意到每类元素最大值和次大值相同,那么当前堆里面已经取出的元素(包含它自己)每一类至少有两个,全拿了一定最优
所以所谓的 “删掉之前的一个 \(2\) 和扔掉之前的一个 \(1\)”,我们并不是真的扔掉了,只是用一个变量维护这个值参与决策即可。
\(Conclusion\)
笔者粗浅的认为:
- 反悔贪心本质其实是在普通贪心的设计上多考虑一个名为撤销的设计步骤,曰之反悔。
- 其往往答案具备凸性,可以考虑其他操作:WQS等
- 暴力启发思路
- 考虑局面的变化情况