学习笔记——反悔贪心

前言#

发现自己并不是很熟练这个人类智慧,于是来补一下。

本文跟随djy 的博客。题目来自于题单

反悔贪心#

其本质大概是考虑一个有一个能被 hack 的贪心策略,这个时候我们先选着当前策略,然后我们考虑加入一个东西,使得之后选这个东西可以抵消掉之前的策略。

一般考虑用堆实现这个东西。感觉和 dp 一样是人类智慧。具体可以看看例题。

例题#

n 天,每天可以买入一支卖出一支,或者什么都不做,求 n 天后能获得的最多的钱。

容易发现,一天内买入一次加上卖出一次相当于什么都没做,于是我们考虑一个贪心,就是每天选择之前价格最低的与之配对,也就是那天买入,这天卖出,这个用一个小根堆维护即可。

但是这个东西显然是错的,因为可能这个最小价格的留给后面某一个配对是更优的,所以我们考虑消除当前的选择,也就是所谓的反悔。

比如说,我们之前选择了 i,j 配对,但是当枚举到 k 的时候,你发现,可能 i,k 配对更优。这个怎么撤销呢?我们发现:

ckci=(ckcj)+(cjci)

就是说,相当于抉择了两次。考虑实际上就是让 j 配对了之后还能与 k 配对,所以就是再次把 cj 加入小根堆就好了

My Code
const int MAXN=3e5+10;
priority_queue<int,vector<int>,greater<int>> q;
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int n,v;cin>>n;
	ll ans=0;
	rep(i,1,n){
		cin>>v;
		if(!q.empty()&&q.top()<v)
			ans+=v-q.top(),q.pop(),q.push(v);
		q.push(v);
	}cout<<ans<<'\n';
	return 0;
}

从这个例题中我们可以总结出反悔贪心的一般套路。就是首先考虑贪心,然后发现这贪心是假的;然后考虑题目中一个元素的『无用』操作,即正着做一次反着做一次相当于没做;然后考虑贪心哪里是错的;最后考虑用加入反元素的方式来撤销之前的抉择。

还是要多做题。

练习题#

n 项工作(1n105),每项工作有截止时间和利润。完成一项工作可以获得它的利润,而每一时刻只能选择完成一项工作。求最多获得的利润。

错误的贪心,就是不考虑优劣性,直接往里面加。那我们考虑维护一个堆,然后按时间排序后依次加入,这样感性理解起来可以加入很多。然后反悔的时候,取出当前堆顶,然后比较当前元素于堆顶的优劣。如果是当前元素更优,那就把之前做堆顶工作的时间拿来做当前工作。

n 个建筑,每个建筑有时间花费 t1 和截止时间 t2。求最多抢修几个建筑(一个修完才能修下一个)。

这题变成了价值为 1 而用时给出。同样考虑错误的贪心,按时间顺序加入,然后每次找前面占时间最多的来替换,需要大根堆。

现在数轴上有 n 个点,从原点出发,每一单位时间可以走一单位距离。到达一个点可以选择花费 ti 的时间获得 1 的价值。求最终能获得多少价值。

首先一个贪心是肯定不会往回求,这要求我们边走边做好策略的选择。这样直接反悔即可。和上面那题一样顺次加入,然后如果无法加入,就把之前花费时间最多的点舍弃掉。

你经营一家商店,上午会进货 ai,下午可以选择卖出 bi,从而满足一位顾客。求最多满足多少顾客。并输出方案。

又是非常显然的反贪。你首先考虑贪心,如果能卖那就一定卖。然后如果某一天商品不够了,就从前面选一个买了商品最多的人,替换掉。

现在有 n 个树坑,每个坑种树有一定收益。现在有 k 棵树可以种,然后两棵树不能相邻,求最大收益。

比较难想啊,看了题解。就是你首先考虑一个错误的贪心,就是每次取出最大的一个数,如果它左右没有种,那就种上,否则就不种。考虑这样在什么时候是不优的。就是你如果选了一个数,结果把这个数换成选两边可能更加优。所以你考虑加入一个东西使得能够反悔。

其实和例题差不多,就是你希望可以选两边的而不选中间,那你就加入 wl+wrwmid,这样,你把序列用链表维护起来,然后选了一个点就把它左右的数从链表中删去,同时把这个点的权值改成上面说的那个。然后每次在大根堆中不断取出,看能不能种。

好强的反悔方式!讨论区 4 倍经验!

n 个人,每个人有两个属性 a,b,把这 n 个人分成 A,B 两个大小分别为 p,s 的集合。使得 A 集合中的人 a 属性的和加上 B 集合中的人 b 属性的和的和最大。输出方案。

考虑一个贪心,就是首先先把前 pa 大的放到 A 集合中的。然后看每一个放到 B 中对最终答案的贡献,看是否更优,如果能更优就加。然后看当前选来放到 B 集合的人,如果没有被选入到 A 中,那么直接加入,答案加上 bi。否则,我们看能不能找一个人来替代它更优,我们假设是 j,那么对答案的贡献是 biai+aj。如果这个贡献是负的,那我当前这个不加也罢!

所以我们当前的策略是,把没有加入 B 的人的 bi,ai,biai 都塞到堆里面。然后每次取出 bi 的堆中最大的和 biai 中最大的加上 ai 中最大的。注意,biai 堆中是两个集合都没有选的,剩下的是 biai 是选入了 A 集合的。然后两个比较一下,加入即可。这样循环 s 次,就求出最后的结果了。注意及时更新堆中的元素。

就是在上面那题的基础上再加上多出一个属性。并且每个人都要属于一个集合。

考虑转化成上一题。你假设大家都加入第一个集合,那么然后 bibiaiciciai。就和上一题一模一样了。

每天可以花费 ai 准备一题,花费 bi 打印一题,然后每天最多准备一题打印一题,求打印 k 题的最小花费。

首先考虑贪心,你可以把这个准备看成是买入一只股票,打印是反向卖出一只股票。现在需要求恰好卖出 k 只股票。如果没有 k 只股票的限制,则直接使用例题的思想就做完了。现在,我们考虑,如何控制它恰好卖出 k 只股票。直观考虑,如果我们每只股票的收益同时增加一个 d,则最终策略的优劣性不变,但是我们会买更多的股票。于是我们二分这个 d,然后每次求最优策略下买的股票的个数就行了。

n 个关卡,对每个关卡,你可以花 ai 代价得到一颗星,也可以花 bi 代价得到两颗星,也可以不玩。问获得 w 颗星最少需要多少时间。并输出方案。

同样考虑贪心,考虑加入的时候贪心有两种方式,一种是一颗星星,另一种是两颗星星。不妨考虑把两颗星星当成花费 biai 的代价在一颗星星的基础上在获得一颗星星。我们进行反悔,可以把一颗星星反悔成不选,然后再另外选一颗变成两星,那么就是 bjai。也可以把两颗星星的反悔成一颗,然后再另外选一个没有星星的变成两颗星星,这样代价就是 aibi+bj。然后我们用 5 个堆分别维护 biaibiaiaibiai

后记#

其实做多了就发现,反悔贪心的最重要的一点就是找到撤销与贪心。

然后迫使每次选择都使决策点加一。

posted @   ZCETHAN  阅读(179)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
主题色彩