这是我的第一篇博客
引入
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望导致结果是全局最好或最优的算法(不保证最后算出来的是全局最优解)。贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。
应用场景
- 活动选择问题:给定一系列活动,每个活动有开始时间和结束时间,选择最大数量互不重叠的活动。
- 区间调度问题:类似于活动选择问题,但更复杂。
- 哈夫曼编码:用于数据压缩,通过贪心算法生成最优的前缀编码。
- 最小生成树问题:如Kruskal算法和Prim算法,通过贪心选择可以构建最小生成树。
- 单源最短路径问题:Dijkstra算法和Bellman-Ford算法,虽然不是纯粹的贪心算法,但它们在每一步都选择当前最短的路径。
确定该题要使用贪心算法后的几个步骤(可能稍微有些麻烦)
1.分解问题:将一个大的问题分解为若干个小的子问题
2.贪心选择:在每个情况下(或者说在每次循环下)寻找当前(局部)的最优解
3.全局最优解:通过局部最优解的组合推导出来的最优解
下面我们来看几道例题
先来一道简单题吧:atcoder abc 352 C-Standing on the shoulder
有 个巨人,编号 1 到 。当巨人 i 站在地面上时,他的肩高为 ,头高为 。
你可以构造一个 1∼ 的排列 P。并根据以下规则堆叠巨人:
- 首先,将巨人 1 放在地上。巨人 1 的肩膀离地高度为 [p1],头部离地高度为 [p1]。
- 对于 =1,2,…, −1依次将巨人 [ +1] 放在巨人 的肩膀上。令巨人 的肩膀离地高度为 ,那么巨人 [ +1] 的肩膀和头部的离地高度都要加上 。
显然,对于不同的排列 ,最上面的巨人 的头部离地高度也不同。请你求出高度的最大值。
原题:C - Standing On The Shoulders (atcoder.jp)
提前工作:因为题目求的是最顶端巨人头部距离地面的最大高度,所以是贪心
实际上当i是最高的那个巨人的时候,他/她的头部高度为 𝐵𝑖 加上他脚踩的肩膀,也就是 ∑𝑗≠𝑖𝑎𝑗 这里j ≠ i的原因很好理解,正常来讲哺乳动物应该不大可能踩到自己的肩膀。由此我们可以列出下列等式
𝐵𝑖+∑𝑖≠𝑗 𝑎𝑗 = 𝐵𝑖−𝐴𝑖+∑aj
右边这个式子相当于我们只需要求出最大的头与肩膀的差就行因为肩膀的高度加起来必定是一样的。所以说其实我们要求的是max(头与肩膀的差)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int main(){
int n,a[maxn],b[maxn];
cin >> n;
for(int i=0;i<n;i++){
cin>>a[i]>>b[i];
}
long long m=0;//m其实就是最大的头与肩膀的差
for(int i=0;i<n;i++){
m=max(m,b[i]-a[i]);
}
long long ans=m;
for(int i=0;i<n;i++){
ans+=a[i];
}
cout<<ans;
}
这道简单题中的贪心部分其实就是
m=max(m,b[i]-a[i]);
接下来上一小点强度
P3626 [APIO2009] 会议中心
链接:P3626 [APIO2009] 会议中心 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Siruseri 政府建造了一座新的会议中心。许多公司对租借会议中心的会堂很 感兴趣,他们希望能够在里面举行会议。
对于一个客户而言,仅当在开会时能够独自占用整个会堂,他才会租借会堂。 会议中心的销售主管认为:最好的策略应该是将会堂租借给尽可能多的客户。
显 然,有可能存在不止一种满足要求的策略。 例如下面的例子。总共有 4 个公司。他们对租借会堂发出了请求,并提出了 他们所需占用会堂的起止日期(如下表所示)。
开始日期 结束日期
公司1 4 9
公司2 9 11
公司3 13 19
公司4 10 17
上例中,最多将会堂租借给两家公司。租借策略分别是租给公司 1 和公司 3, 或是公司 2 和公司 3,也可以是公司 1 和公司 4。注意会议中心一天最多租借给 一个公司,所以公司 1 和公司 2 不能同时租借会议中心,因为他们在第九天重合 了。
销售主管为了公平起见,决定按照如下的程序来确定选择何种租借策略:首 先,将租借给客户数量最多的策略作为候选,将所有的公司按照他们发出请求的 顺序编号。对于候选策略,将策略中的每家公司的编号按升序排列。最后,选出 其中字典序最小1的候选策略作为最终的策略。
例中,会堂最终将被租借给公司 1 和公司 3:3 个候选策略是 {(1,3),(2,3),(1,4)}。而在字典序中(1,3)<(1,4)<(2,3)。 你的任务是帮助销售主管确定应该将会堂租借给哪些公司。
第一问的做法非常多,而且并不难,但是如果加上第二问,那就是“简(e)单(xin)”题了
第二问首先考虑的做法有dp(动态规划)和贪心。
我们先来看看dp,一般字典序的题目大家都可以有限考虑dp
dp在这道题里第一问在线段树优化后复杂度为O(n log n)了,而第二问才是重点,用dp的话空间时间全都超了,复杂度为𝑛𝑙𝑜𝑔2𝑛 最终大概只能拿50分左右。
或许dp真的能做出了但是我没做出来(哭
那这道题该如何去做呢?
为了方便大家第二问理解,我们假设我们求出来了第一问的最大值为m然后我们把满足最优解的线段集合记作t数组,注意t必须要满足ans是它的子集且可以为空集,这里我们新加个数组吧,叫y。那么我们就要满足首先要满足的是插入后一定可以使最后集合里的线段总数仍然是t。如果不满足这个条件,就立刻停止贪心,因为不满足第一问的条件。
还有一个特点是每条线段的下一条可行线段是固定的
可能有人有个疑问,那么插入后对答案有影响的区域该怎么找呀?
插入后对答案有影响的区域实际上为t中 𝑦𝑙𝑒𝑓𝑡 到 𝑦𝑟𝑖𝑔ℎ𝑡 的区间,其实也是前驱和后继之间的那段区间
用数学表示的话可能更容易理解:是[ 𝑦𝑙𝑒𝑓𝑡+1 , 𝑦𝑟𝑖𝑔ℎ𝑡−1 ]的那段区间
橘色这部分就是可能被插入线段影响的区间
我们设 𝑓𝑖 表示以第 个线段为首后能插入的最多线段数,f(l,r)意思是从最左边到最右边的线段数量。对于每一个确定的区间,我们放置线段的数量都是确定的。于是我们便可以使用贪心,将这一整个线段划分为多个子区间,最终的答案就是每个子区间的f加起来等于m,求f。
那么对于这个有可能被影响的区间,我们又分为三个区间,在插入线段的左边,右边以及自己本身所在的区间。那么凭借尝试来讲中间这段的f必定是1,因为我们把它放在那里,且时间又不能从重和。
所以最终的条件可以写为
𝑎𝑛𝑠=𝑓(𝑦𝑙𝑒𝑓𝑡.𝑟+1,𝑦𝑖.𝑙−1)+𝑓(𝑦𝑖.𝑟+1,𝑦𝑟𝑖𝑔ℎ𝑡.𝑙−1)
这里i表示的是插入的这段区间 𝑦𝑙𝑒𝑓𝑡 表示的是可求范围内插入线段左边的那块区域, 𝑦𝑟𝑖𝑔ℎ𝑡 同理
那么具体又该如何去做呢,由于我前面说过了每条线段的下一条可行线段是固定的,所以我们可以对每条路径做倍增,然后可以轻松的在logn的时间求出每个区域的线段树数量,至于求第一条线段(第二问)就可以用二分函数(lowerbound+upperbound)去完成了!
什么情况下不能使用贪心
许多人学完贪心后都会觉得,贪心是万能的,但其实并不是这样。
举个栗子,背包问题中变不可以使用贪心。大家可以自己尝试构建反例去尝试
从零开始学习背包问题(一) - 知乎 (zhihu.com)这篇马融老师写的文章非常好,详细的解释了为啥背包问题中不能使用贪心。
除了背包问题外,还有旅行商问题,矩阵链乘问题,图的最小生成树问题(虽然Kruskal算法和Prim算法看似是贪心算法,但它们实际上更接近于动态规划和贪心算法的结合。对于某些特定的图结构,纯粹的贪心算法可能无法找到最小生成树。)等等...
有问题的话欢迎指出,会努力改进的!!!
觉得好的话就点个赞吧