「反悔贪心」的再理解
「反悔贪心」的再理解
0x00 引子
建议使用『进阶方法篇』反悔贪心系列 配合食用。
自从 CSP-S 被第一题搏杀之后,我的内心一直耿耿于怀,总想着怎么一雪前耻。在长达一周的做题和翻博客中,一个奇怪的概念逐渐在我心中长了出来,它就是 占位符。
这篇文章会系统性地盘一下占位符的概念,并尝试从原理上解释它是怎么构造出来的,以及什么时候该用它。
这也算弥补了一个遗憾吧。
0x10 无抽象占位的反悔贪心
先不管占位符那个抽象词,这章不提它,大家自己找找感觉,看看占位符到底是啥。
我们先从几道题入手,搞清楚反悔贪心大概是个什么工作原理。
提起反悔贪心,祖师爷级别的题目必须是:
P4053 [JSOI2007] 建筑抢修
这题太经典了,以至于很多小朋友都会做(虽然我到现在才会)。
贪心的本质是啥?在我看来,就是想用局部最优去代换全局最优,因为全局最优(比如 DP)复杂度太高了,跑不动,只能退而求其次。但有时候局部最优也是个坑,选了这个,后面更好的就选不了了。那贪心就废了吗?也没完全废。
如果局部最优和全局最优差得不大,也就是可以通过一步简单的修改,把局部最优变成全局最优,那么就不用管了。
这道题就是个典型。
一般的贪心思路:把所有建筑按截止时间排个序,从前往后搞,能修就修。
但这样很容易被卡掉,比如你修了一个耗时巨长的,导致后面三个短的都修不完。
那我们能不能搞个“后悔药”?
经过长时间的思考(其实是看题解),发现确实可以。因为任何一个操作不会对后面的操作造成逻辑上的死锁,只会占用时间。
于是我们搞个大根堆,维护过去修过的任务里耗时最长的那个。如果我们遇到一个新任务修不完了,但它比堆顶那个耗时短,我们就反悔:把堆顶那个踢掉,换成修现在这个。
再看个加强版。
P2949 [USACO09OPEN] Work Scheduling G
这题只是把“耗时”变成了 1,加了个权值。
那我们将计就计,按截止时间排序。有空位直接做,扔进小根堆。
没空位了?看看堆顶(以前做过的利润最低的任务)。如果现在的利润比它高,直接踢掉那个,换成现在这个高薪的。
再加强一下。
P2107 小 Z 的 AK 计划
这题简单来说就是你有 \(m\) 的时间限制,路上有机房,进机房 AK 要花时间,走路也要花时间。
代价是:\(\displaystyle\sum_{i=last}^{now}{x_i}+t_{now}\) 这题也很版,只是代价会随时变动(路费一直在涨)。
我们将计就计,把路费从式子里掏出来,单独算。
对于每一个新遇到的机房,先把路程加到前缀和里,假装我们要 AK 它,把时间加进去,并把 AK 的耗时扔到大根堆里。
如果总时间爆了 \(m\) 咋整?聪明的孩子自己会动动手指把堆顶最大的那个扔出去的。
P3545 [POI 2012] HUR-Warehouse Store
这题把时间换成了商品库存,AK 的时间换成了客人要货的数量。
实际上……好像还不太一样?
算了,本质差不多。
我们要维护“已经满足的客人”的需求量。如果库存不够了,就看堆顶那个要货最多的客人,如果现在的客人要得少,就反悔不卖给之前那个神人了,省下来的货卖给现在这个。
后面怎么 push 怎么 pop,聪明的孩子依然会自己动的。
中场休息:做一下 [猫粮],也许能成为有猫耳的猫娘哦(大雾)
P11628 [WC2025] 猫粮
实际上这道题也是贪心,但不是反悔贪心。放在这纯粹是为了防止你们做题做魔怔了,看见贪心就想反悔。
坏了,昨天做的今天忘了咋整,不能我的大脑变成猫娘的形状了吧……
喵~ 米纳,让我们来看看这道喵粮题吧~
(此处省略一万字发癫过程,大意就是这题是构造 + 匹配,利用随机性质去填坑,而不是先选再撤回,喵粮全被我吃了,这题撤不回来的喵。)
大分讨还是太难了,狗修金快给我几袋优质喵粮补偿我~(逃)
咳咳,刚才被盗号了。继续讲反贪。
0x20 有抽象占位符的反悔贪心
我认为各位在做过或者看过前面的题解之后,可能对于占位符已经有了一点感觉了,下面会更详细的介绍占位符这个概念。
占位符简介
我根据询问 ChatGPT 、 Copilot 、 Gemini 、 Claude 四个AI的结果,使用 Gemini 总结出了一点心得,或许能够给各位和以后的自己带来一丝触动(?
占位符实际上是反悔贪心中构造的一种虚拟决策节点。
它不代表原始问题中的实体,而是 撤销上一轮贪心决策并转向替代方案 这个复杂行为的数值化身。它将“修改历史”的操作,转化成了“在当前进行一次新的选择”。
我们观察上面的四道反贪,不难发现:
| 题目 | 占位符 | 转移 |
|---|---|---|
| 建筑抢修 | 先前建筑用时 | 直接替换 |
| work scheduling G | 先前建筑权值 | 替换并更改答案 |
| 小Z的AK计划 | AK用时 | 尝试去除后满足条件,之后替换并更改答案 |
| P3545 | 需要的货物数量 | 尝试去除后满足条件,之后替换并更改答案 |
完全一样啊。
Gemini 总结说:
占位符是反悔操作的差价凭证,其权值是“新方案收益”和“旧方案收益”的差值。
它实际上是利用了代数抵消的原理工作,它利用了 \(X+(Y-X)=Y\) 的数学性质,让看似在累加的操作变成了替换。
Gemini 比喻说:
在优先队列中,占位符是一个时光机按钮。
- 普通元素: 我现在要做这件事
- 占位符元素:我现在要撤回我刚才干的,改做另一件事
而占位符的作用有三个:
- 延迟决策 允许未来修正错误,当前可以由着性子贪心。
- 线性化逻辑 将复杂的回溯或者状态转移,转化为堆上的推入/弹出操作
- 模拟最小费用流的反向边 在图论下,占位符就是网络流中的“退流”边,选择了话,就是让流流回源点,再流向另一条路径。
CF865D Buy Low Sell High
经典的股票买卖问题。占位符的标准体现。
我们每天拿到价格 \(p\),先把 \(p\) 扔进小根堆(当作买入机会)。
如果 \(p>\) 堆顶,说明有钱赚!我们取出堆顶 \(x\),假装赚了 \(p-x\)。
但这里有个问题:万一我不该在今天卖,而是应该留到后面更贵的日子卖呢?
或者说,我是不是把今天当成了一个买入点更合适?
我们在 pop 掉 \(x\) 并计算利润的同时,要再往堆里扔一个 \(p\)。这就是占位符。
为什么要 push 两次?代码里通常是这样写的:
q.push(p);
if(p>q.top()){
ans+=p-q.top();
q.pop();
q.push(p);
}
-
第一个
push:把今天当作一个新的买入点,以后可以卖出。 -
第二个
push:如果我们以后用了这个占位符(即把它作为买入价配对了一个更贵的 \(p_{nxt}\)),收益计算过程如下:\((p-\min)+(p_{nxt}-p)=p_{nxt}-\min\)中间的 \(p\) 奇妙地抵消了!这在物理上等价于:我们从未在第 \(i\) 天卖出,而是直接把 \(\min\) 持有到了 \(nxt\) 天才卖出。这个占位符 \(p\),就是那张“反悔并补差价”的票据。
P1484 种树 及加强版 P1792
这道题也要用到上面的占位符思想。
我们考虑直接构造占位符,
对于当前种一棵树的操作,会使得两边的树不能种。那么我撤销这个操作种两边的树的话,他们的代价是固定的,是:
那么我们在 \(now\) 树上操作过后,因为它的两侧 \(left\) 和 \(right\) 树不能种了,我们就将这两棵树和当前树合在一起,并将这棵树的价值改成 \(a_{left}+a_{right}-a_{now}\) ,作为占位符使用。
但是用数组的话会消耗很大空间啊,咋整呢……双向链表!
请注意 P1792 这道题需要你搞一个循环,但 P1484 不需要。
接下来看一个加强版。
P3620 [APIO / CTSC2007] 数据备份
这道题不难看出来和上一个种树实际上很像,你把线化成点,选了一条线,两侧的线就不能选了,实际上和上一道题是一样的。
操作实际上没有什么不同,只是要看出来这个转化比较麻烦,如果看不出来的话就做不出来。
0x30 暴力判定法则
如果各位还没悟出来怎么判定能不能用反悔贪心,试试这个暴力方法(P3620 我就这么做的):
- 试探:我如果撤销现在的操作换成另一个,差价是多少?
- 验证:这个差价是一个固定的数吗?还是会随着其他变化乱变?
- 结论:
- 如果是固定的数(只和局部有关)\(\rightarrow\) 占位符能造出来 \(\rightarrow\) 可以用反悔贪心。
- 如果算不清或者牵一发而动全身 \(\rightarrow\) 占位符造不出来 \(\rightarrow\) 放弃吧,直接去想 DP 或者网络流吧。
感谢各位观看本篇文章,如有疏忽还希望各位指正。

浙公网安备 33010602011771号