贪心思想杂谈及证明方法归纳
贪心思想
贪心就是局部最优解变成了全局最优解。
下面介绍一种邻项交换的证明,以 P1080 [NOIP2012 提高组] 国王游戏 为例。
设 是相邻的两个元素,那么很重要的一点是对于 和 的其他元素,这两者之间的交换是不会改变其答案的。
考虑什么时候 和 要交换。根据题意,我们要的是最大值最小,交换前的最大值为 ,交换后的最大值为 ,当前者大于后者时,也就是交换后能使得最大值变得更小,那么我们就会选择交换,当然,前面的式子还可以进一步化简。
容易观察到:
所以将条件转化为 ,即 。
由此可见,我们要将 升序排列(当前者大于后者时会交换)。
另一种想法是考虑贡献,下面以这题为例:
给出 个区间 ,求最少的点的个数使得点能覆盖到所有区间。
首先,题目中每个区间的贡献为 ,而且我必须把区间覆盖,假如现在已经有一个点在区间上,那么我完全可以跳过,倘若没有,那我一定会选出一个位置,既然答案一定会加一,那我就考虑怎么样才能让我选的这个点价值最大,即在之后还能覆盖到最多的点。
于是我们很自然的想到,选择右端点的数一定是最优的,先感性理解一下。
现在有三条线段,对于第一条,我应该在哪里选择呢?显然是右端点,这样更有希望够到后面的线段,因为右端点是递增的。
我们可以设取了点 且 ,那么对于 覆盖到的区间构成的集合记为 ,对于 覆盖到的区间构成的集合记为 ,因为前面的区间一定已经被覆盖了,所以不再考虑,观察后面的区间,因为右端点递增,所以发现 。
【例题】CF1203F1 Complete the Projects (easy version)
对于这题,假如能完成所有项目,那么总贡献仍然为 ,每个项目贡献为 。所以考虑贪心。
一个比较直观的想法是优先做 的项目,因为这相当于是在“回血”,如果连某个 的项目都无法完成,那么就更别指望后面 的项目带来的贡献了,这一部分正确性显然。
那么对于 的又是什么策略呢?你可能会想按照 降序排列,但很容易找出反例。
n = 2 , r = 1000
1 -1
1000 -2
具体的我们根据相邻的两个项来求这个东西。设 是相邻的两个元素,那么交换前能满足条件,即 且 ,整理得 。
对于交换后同理可得 。什么时候更优呢?显然是限制更宽的时候,也即 时,需要交换。
这里我们发现和上文国王游戏一样优秀的性质(注意此时 ):
所以上式可以化简为 ,即 ,所以可知需要按照和降序排序。
这里简单提一下如何化简的。首先假如 左边的 作为最大值出现,那么右边的 无论如何比它大,不符合题意,右边 作为最大值时答案显然,无需比较所以选 作为最大值。
换一个角度,也可以考虑那个下界要求更高,因为做了一个任务后能力值一定减少了,所以后面的那个显然要求更高。
上面说的都有一个共性,也就是总贡献是一定的,反正都要完成。那假如改为求最大贡献值呢?
参考本题的 hard version。我们使用 DP 在原有基础上进行选择即可,另一种可行的方法是使用反悔贪心,但是本文不做详细展开。
关于这种方法,还可以参考 P4823 [TJOI2013] 拯救小矮人。
首先感性的分析,这也是贪心很重要的一环,我们肯定是让“逃生能力”弱的小矮人先行离开,否则可能就再也没有离开的机会了!这里的“逃生能力”定义为 。另一种看法是肯定要让个子高的在下面,手长的在上面,那么哪个才是正确的呢?
考虑邻项交换,定义如下。
在这里为了直观一点,假设两个人都在最前面,并且都能逃脱(注意这只是假设)。
交换前
交换后
整理可得 时需要交换,那么升序排列即可。但是到这还没完,因为我们求出的只是所有人走完的最优方案,但是不一定走完,所以套一个背包来求最大值。
P1248,P1561,P2123:这三题属于同一个问题,让我们来看看。
首先从最直观的 P2123 开始,容易想到用邻项交换解决问题,设 是相邻的两个元素,一个显然的事情是 对应的值一定是大于 对应的值的,这提供了很大方便。
具体的,在交换前设 ,则 的值为 ,交换后, 的值变成 。外面的加号可以算进去,那么前者就变成了 ,后者变成了 。注意到第一项是一样的,那么不管。
同时减去 得:
这个式子,似乎和之前都不一样,感觉不能进一步化简了,不妨就这么交上去,你会发现,过了,但是,真的是对的吗?接下来给出简单过程,详细请参考【ouuan】浅谈邻项交换排序的应用以及需要注意的问题。
首先我们考虑邻项交换的本质是什么,简单地说可以看作是一个排序的策略,使得答案最优,那么排序策略需要一些重要的性质,详见这篇博客。而我们求出的这个东西却不满足 STL 需要的性质,所以是不能用来排序的。
于是我们尝试在原有基础上增加点什么,一个很容易想到的尝试是把 找出来,改为 的升序排列,这样也很符合直观,也就是让 更小。同样也可证明这样做是符合要求的,具体详见上文给出的博客。另一种可行的想法是分类讨论,这样也许你就能得到另一种可行的做法,也就是按照 的大小关系进行排序。
接着看一下另外两题为什么和这题是一样的,A,B 车间用时就是 ,c 就是最长用时,另一题略。
当然,前文所讲都是序列上的贪心,实际上贪心很多时候不会单独出现,而是会结合其他结构一起出现,有时候单纯只是一种思想。
【例题】P4574 [CQOI2013] 二进制A+B
本题可以使用数位 DP 解决,但是代码较为繁琐。但是假如使用贪心解答,代码就简单很多,但是思维量却加大了,所以我们需要做一个权衡。
int B(int x){return __builtin_popcountll(x);}
int C(int x){int r=0;while(x)r++,x/=2;return r;}
void solve(){
cin>>a>>b>>c;mx=max({C(a),C(b),C(c)});
a=B(a),b=B(b),c=B(c);if(a<b)swap(a,b);
if(a+b<c) {cout<<"-1";return;}
if(c==1) ans=1ll<<(a+b-1);
else if(c<b) ans=(1ll<<(a+b-c))+((1ll<<(c-1))-1)*2;
else if(c==b) ans=(1ll<<a)-1+(1ll<<b)-1;
else if(c<=a) ans=(1ll<<a)+(1ll<<c)-1-(1ll<<(c-b));
else if(c<a+b) ans=(1ll<<c+1)-1-(1ll<<(2*c-a-b));
else if(c==a+b) ans=(1ll<<(a+b))-1;
else ans=-1; if(C(ans)>mx)ans=-1;
cout<<ans<<endl;
}
【例题】P3294 [SCOI2016] 背单词
这题就不是简单的贪心了,首先使用 Trie 树把所有字符串倒序存起来,方便表示后缀。
然后贪心的考虑三个操作,首先发现第一个操作代价太大,显然超过了后面的总和,必然不选。这一想法就为字符串序列提供了一个隐性的条件,也就是一个单词的所有后缀都必须出现在这个单词前面。
然后分析一下二三两个操作,我们发现二其实就是三在找不到前缀时的答案,那么就可以只考虑三操作。显然,我们希望后缀中最大的那一个离当前字符串尽可能近。此时我们重构一下 Trie 树,只保留关键节点,也就是让子树大小变成字符串数量,因为我们要尽可能近,所以每个父节点的子树一定是按照大小从小到大排列的。
最后就可以考虑计算答案了,按照 dfn 序记录下前面出现过的个数和自己的位置即可。
【例题】P3243 [HNOI2015] 菜肴制作
这题非常好,首先明确题目要求是最小的能先就先,不是字典序最小(4 1 2 3
优于 2 3 4 1
)。
那么我们这样贪心,显然,假如能把最大的 放在末尾,那么一定是最好的,因为理想情况就是 1 2 3 4
,那么我们可以反向建边,这样问题就转化为了字典序问题,可以直接拓扑排序(每次优先取最大点)。
此外,我觉得当题目中出现类似“Note that you don't have to maximise it.”(不需要求最大化)的时候可以选择想一下贪心。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】