反悔贪心

反悔贪心

0x00 引子

作为一个比较诡异的算法,我一直没能悟到它的精髓,最近来看,占位符好像是反悔贪心的核心,所以本文会着重于体现占位符的重要性和占位符的应用。本文也算收拾一下之前的烂摊子,把思路整理起来,统计成册,便于最后的复习与反思。

建议使用 『进阶方法篇』反悔贪心系列 配合食用。

好像有彩蛋......但不在最后~


0x10 无抽象占位的反悔贪心

我们先不提占位符这个抽象概念,在这一章并不会提到占位符,请读者自行寻找占位符可能是什么。

我们先从几道题开始入手,让各位了解反悔贪心的工作原理,并对于占位符有点印象,后面会更加深刻的讲这个占位符。

提起反悔贪心,必不能少的一定是:

[P4053 JSOI2007] 建筑抢修

这道题过于经典了,以至于很多小朋友都会做(我到现在才会)

贪心的本质是什么,在我看来,是要用局部最优解去代换全局最优解,原因是全局最优解的时间复杂度太高了,我们只能退而求其次。然而有的时候局部最优解并不能代表全局最优解,那么贪心就废掉了。然而真的废了吗?实际上并没有。

贪心的本质是局部最优解,全局最优解的代表是 DP 和拓扑排序,两者的差距还挺大的。那我们可以估算一下,是否局部最优解和全局最优解之间差的不是特别大,就是可以通过一步 或者简单的修改,可以将局部最优解转换成全局最优解。

这道题就是一个典型。

我们考虑贪心去搞这个东西。怎么贪心呢?把所有的建筑最后的时间排一个序,然后从前往后找,到哪里就算到哪里了。

然而这个东西很容易被卡掉。

看这个数据:

2
100 100
5 101
5 101

你明明可以两个都搞的,但你在这里仅仅只能得到一个的价值。

那么我们考虑能不能使用一次更改让局部最优解变成全局最优解呢?

我们来考虑一下记录过去的操作,撤销过去的操作是否可行。经过我长时间的思考,发现确实可以。因为任何一个操作不会对于后面的操作造成影响,这里的造成影响指的是 时间的增加/贡献的增加会被造成影响。

于是我们使用一个大根堆来维护过去已经完成的操作,并且对于选的更烂的情况就直接 popans--

接下来我们要去找个加强版。

[P2949 USACO09OPEN] Work Scheduling G

不难发现,这里只是给加了个权值,然后把原来的时间长度去掉了。

那我们将计就计,直接按照权值排序,扔进小根堆里面等着 pop 就行了。

再加强一下。

P2107 小 Z 的 AK 计划

这道题简单来说就是说你有一个 \(m\) 的时间限制,然后每个东西可以选择拿或者不拿,拿了的话就是

\[\displaystyle \sum_{i=last}^{now}{x_i}+t_{now} \]

的代价。其中, \(last\) 是上一个拿的坐标, \(now\) 很明显吧......

这道题也很版,只是代价会随时变动。

我们将计就计,把 \(\displaystyle \sum_{i=last}^{now}{x_i}\) 从原来的式子里面掏出来,然后塞到 \(t\) 里。

这个 \(t\) 我们怎么处理呢?我们对于每一个新遇到的机房,直接先把它的路程加到前缀和里面,并且也把 AK 用的时间加到里面,并把 AK 的时间扔到大根堆里面,后面怎么做就不用我教了吧,聪明的孩子自己会动的。(动什么呢?好难猜啊......原来是动动手指敲键盘啊)

哦对,最后不要忘记每次循环结束后记录答案,因为他在任意一个地方都可以结束。

我们来看一个小z的AK计划的加强。

[P3545 POI 2012] HUR-Warehouse Store

这道题就是把时间换成了商品,AK的时间换成了客人要的商品的数量,走的路程变成了进的货,要维护的把最大时间变成了不能后退。

实际上好像还不太一样……算了。

因为要维护哪一个客人的需求被满足了,所以我们要同时记录一个 i 在堆里面。后面怎么扔进去,怎么 pop 这个聪明的孩子会自己动的。

要不,中场休息一下~做一下 [猫粮],也许能成为有猫耳的猫娘哦~


[P11628 WC2025] 猫粮

实际上这道题也是贪心,但不是反悔贪心,只是用来预防一下你们把贪心误写成反贪。

坏了,昨天做的今天忘了咋整,不能我的大脑变成猫娘的形状了吧......

喵~米纳,让我们来看看这道喵粮题吧~

看,那就是我最喜欢吃的优质喵粮,但好遗憾呢,那个喵粮很不容易抢到,于是只能随机的给一个喵,这样会让狗修金很难处理呢喵。那个是我并不是很喜欢吃的喵粮,虽然不好吃,还是可以让我饱腹的喵~因为没有那么好吃,所以主人放在哪个喵的前面,哪个喵就会吃掉它的。

狗修金向我发出了请求,让我帮助他完成对每一个喵的喵粮的分配,并让每一个喵都能吃饱,而且所有的猫粮加起来恰好能让每一个喵吃饱,总共也只有两倍喵的个数袋喵粮。

那我们就要好好想一想,怎么利用这个随机性质啊喵~我们可以发现,我们可以先随机分配一个优质喵粮,然后再那个喵前面放一个普通喵粮,让它吃饱呢喵。但是有一些喵粮并不能正好配成一对,使得他们能让一个喵吃饱/不多吃啊喵。

剩下的我们这样考虑:剩下的普通喵粮互相配对,看看能不能用两袋喵粮让一个喵吃饱,对于剩下的优质喵粮就好麻烦呢,因为喵们的争抢,它的分配是随机的,所以我们对于优质喵粮大于两袋的情况,就要让每一袋喵粮都正好是喵们饱食度的一半,等于两袋的话,就直接配对看看能不能行就好了喵~

呼呼呼呼,喵~↑大分讨还是太难了,狗修金快给我几袋优质喵粮补偿我~(逃)


欸欸欸?我怎么了?这么累,身上怎么还有猫的气味......算了算了,继续讲反贪吧。

0x20 有抽象占位符的反悔贪心

我认为各位在做过或者看过前面的题解之后,可能对于占位符已经有了一点感觉了,下面会更详细的介绍占位符这个概念。

占位符简介

我根据询问 ChatGPT 、 Copilot 、 Gemini 、 Claude 四个AI的结果,使用 Gemini 总结出了一点心得,或许能够给各位和以后的自己带来一丝触动(?

占位符实际上是反悔贪心中构造的一种虚拟决策节点。

它不代表原始问题中的实体,而是 撤销上一轮贪心决策并转向替代方案 这个复杂行为的数值化身。它将“修改历史”的操作,转化成了“在当前进行一次新的选择”。

我们观察上面的四道反贪,不难发现:

题目 占位符 转移
建筑抢修 先前建筑用时 直接替换
work scheduling G 先前建筑权值 替换并更改答案
小Z的AK计划 AK用时 尝试去除后满足条件,之后替换并更改答案
P3545 需要的货物数量 尝试去除后满足条件,之后替换并更改答案

完全一样啊。

Gemini 总结说:

占位符是反悔操作的差价凭证,其权值是“新方案收益”和“旧方案收益”的差值。

它实际上是利用了代数抵消的原理工作,它利用了 \(X+(Y-X)=Y\) 的数学性质,让看似在累加的操作变成了替换。

Gemini 比喻说:

在优先队列中,占位符是一个时光机按钮。

  • 普通元素: 我现在要做这件事
  • 占位符元素:我现在要撤回我刚才干的,改做另一件事

而占位符的作用有三个:

  1. 延迟决策 允许未来修正错误,当前可以由着性子贪心。
  2. 线性化逻辑 将复杂的回溯或者状态转移,转化为堆上的推入/弹出操作
  3. 模拟最小费用流的反向边 在图论下,占位符就是网络流中的“退流”边,选择了话,就是让流流回源点,再流向另一条路径。

那么有人要问了,占位符的应用条件是什么?

必须同时满足:

  1. 决策具有互斥性或者配对行:做了一个选择会导致另一个不可用。
  2. 反悔代价可以量化而且结构简单:撤销旧的换成新的,这个过程能够用一个加减法公式表示(如 \(A+B-C\) )。
  3. 区间替换不破坏全局合法性:反悔操作只影响局部,比如只影响相邻节点或当前持仓,不会导致之前的其他决策突然变得不合法。
  4. 目标函数是线性的或凸的:通常是最大收益最小代价。

绝对不可用的情况:

  1. 决策不可逆:如果资源消耗是永久的,且非数值交换的不能用。
  2. 依赖关系极其复杂:如果选择了 \(A\) ,导致 \(B,C,...\) 的属性全部发生非线性的变化,无法用一个简单的占位符数值概括这种变化也不能用。
  3. 状态空间非单调:如果未来的最优解和当前的最优解没有通过替换可达的路径,也不能用。

而占位符是反悔贪心的灵魂,没有占位符的反悔贪心并不能称之为反悔贪心。也就是说,反悔贪心成立的条件和占位符成立的条件是相等的。

扩展:实际上我们向外看,在网络流中,实际上反悔贪心是最小费用最大流的特例,在网络流中我们使用“反向边”来实现反悔。

我建议各位可以尝试使用一下这个判定法则。在文章末尾还有更直接的判定法则。

CF865D Buy Low Sell High

经典的股票买卖问题。占位符的标准体现。

我们考虑每一天都买个股票,但是并不是真买,就是把股票的价格扔到小根堆里面,如果当前的股票价格比堆顶贵的话,就直接 pop 计算答案。但是这样就有人说了,要是有一天的时候比当前的价格更优怎么办?我们在 pop 的时候也要向里面扔一个占位符,也就是当前的价格,代表了你随时都可以替代这个价格。

那么推进去这个占位符了之后,是不是就不用往里面丢股票的价格了?答案是否定的。

我们考虑这两个 push 的含义。第一个是占位符,如果这个被使用了的话,那么也就代表着有更优的替代了它的购买,第二个是当前的股票价格,如果这个被使用的话,也就是说这个可以带来利润。如果两个都购买的话,我们发现占位符不在了,也就是说我不用在这一天购买,这一天的价格也不在了,也就代表了我们卖出了这一天的股票,这两个操作也就代表着我们从卖出变成了买入。,并没有违反题目的描述。所以这个逻辑和这个代码也就是正确的。

下面是......奇怪的 DP 改贪心?

P1484 种树 及加强版 [P1792 国家集训队] 种树

这道题也要用到上面的占位符思想。

我们考虑直接构造占位符,

对于当前种一棵树的操作,会使得两边的树不能种。那么我撤销这个操作种两边的树的话,他们的代价是固定的,是:

\[a_{left}+a_{right}-a_{now} \]

那么我们在 \(now\) 树上操作过后,因为它的两侧 \(left\)\(right\) 树不能种了,我们就将这两棵树和当前树合在一起,并将这棵树的价值改成 \(a_{left}+a_{right}-a_{now}\) ,作为占位符使用。

但是用数组的话会消耗很大空间啊,咋整呢……双向链表!

请注意 P1792 这道题需要你搞一个循环,但 P1484 不需要。

接下来看一个加强版。

[P3620 APIO/CTSC2007] 数据备份

这道题不难看出来和上一个种树实际上很像,你把线化成点,选了一条线,两侧的线就不能选了,实际上和上一道题是一样的。

操作实际上没有什么不同,只是要看出来这个转化比较麻烦,如果看不出来的话就做不出来。

0x30 暴力判定法则

如果各位还没能悟出来怎么判定的话,可以使用这个方法尝试一下。

  1. 试探:我如果撤销现在的操作换成另一个,差价是多少?
  2. 验证:这个差价是一个固定的数吗?还是会随着其他变化而变化?
  3. 结论:
    • 如果是固定的数(只和局部有关)\(\rarr\) 占位符成立 \(\rarr\) 可以用反悔贪心。
    • 如果不行 \(\rarr\) 占位符不成立 \(\rarr\) 放弃直接考虑 DP 和网络流。
posted @ 2026-01-14 14:58  小林琴奈  阅读(0)  评论(0)    收藏  举报