贪心思想是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,如果要得到整个问题的最优答案,那么每一步都要尽可能的得到最优的答案。
首先初赛必然无法考察贪心的证明。聚焦在贪心的经典题型,又因为贪心算法,方便与其他知识点关联,比如结构体排序后贪心,比如二分答案里做贪心,所以往往代码量和思维度都适合放在压轴题的位置。
解决初赛中的贪心问题,先要熟悉贪心的常见题型。
最常见的贪心有两种。
第二种方式基本要用到“优先队列”这种数据结构,在CSP-J的组别里不可能出现,因为只需要准备第一种贪心。
举个栗子 USACO DEC07 超级书架
题意是给定n个正整数和一个正整数k,问最少选择多少个正整数使得它们之和大于等于k。
很显然这类问题的求解,就是排序 + 统计。
1//题目的完整代码 2#include
哈夫曼树是一种二叉树,用于数据压缩。它的构建方式是通过对一个数据集中的元素进行频率统计,然后将频率较小的元素放在树的底部(远端),频率较大的元素放在树的顶部(近端),以此来减小数据的存储空间。
在哈夫曼树中,从根节点到某个叶子节点的路径上的编码,就是该叶子节点所表示字符的哈夫曼编码。
哈夫曼树的构建可以使用贪心算法来实现,时间复杂度为O(nlogn),其中�n为数据集中元素的个数。
具体构建方式是,首先将所有元素按照出现频率从小到大排序,然后选取频率最小的两个元素作为左右子节点,将它们的频率相加作为父节点的频率,然后将父节点插入到排序后的元素列表中,重复上述步骤直到只剩下一个根节点为止。最后,从根节点开始递归遍历哈夫曼树,将每个叶子节点所表示的字符及其对应的哈夫曼编码存储起来。
假设有如下8个字符及对应的权值:
字符 | 权值 |
---|---|
A | 2 |
B | 3 |
C | 7 |
D | 10 |
E | 11 |
F | 13 |
G | 14 |
H | 20 |
我们可以按照以下步骤构造哈夫曼树:
将所有节点按照权值从小到大排序,得到 2,3,7,10,11,13,14,20。
选取权值最小的两个节点2和3,将它们合并为一个新节点,其权值为2+3=5。这个新节点代表的子树有2和3两个节点。
将得到的新节点5插入集合中,集合变为 5,7,10,11,13,14,20。
选取权值最小的两个节点5和7,将它们合并为一个新节点,其权值为5+7=12。这个新节点代表的子树有5和7两个节点。
将得到的新节点12插入集合中,集合变为 10,11,12,13,14,20。
选取权值最小的两个节点10和11,将它们合并为一个新节点,其权值为10+11=21。这个新节点代表的子树有10和11两个节点。
将得到的新节点21插入集合中,集合变为 12,13,14,20,21。
选取权值最小的两个节点12和13,将它们合并为一个新节点,其权值为12+13=25。这个新节点代表的子树有12和13两个节点。
将得到的新节点25插入集合中,集合变为 14,20,21,25。
选取权值最小的两个节点14和20,将它们合并为一个新节点,其权值为14+20=34。这个新节点代表的子树有14和20两个节点。
将得到的新节点34插入集合中,集合变为 21, 25, 34。
选取权值最小的两个节点21和25,将它们合并为一个新节点,其权值为21+25=46。这个新节点代表的子树有21和25两个节点。
将得到的新节点46插入集合中,集合变为 34, 46。
选取权值最小的两个节点34和46,将它们合并为一个新节点,其权值为34+46=80。这个新节点代表的子树有34和46两个节点。
哈夫曼树构建完成,根节点的权值为80,左子树的权值为34,右子树的权值为46。
下图为本例的哈夫曼树:
构建完哈夫曼树后,从哈夫曼树的根节点开始,遍历左子树的路径为0,遍历右子树的路径为1,则每个叶子节点对应的字符编码为从根节点到该叶子节点经过的路径上的0和1序列。注意,由于哈夫曼树是一棵前缀编码树,因此每个字符的编码不会是另一个字符编码的前缀。
本例中所有节点对应的编码如下:
字符 | 权值 | 编码 |
---|---|---|
A | 2 | 11000 |
B | 3 | 11001 |
C | 7 | 111 |
D | 10 | 100 |
E | 11 | 101 |
F | 13 | 111 |
G | 14 | 00 |
H | 20 | 01 |
举个栗子 leetcode 435 无重叠的子区间
给定一个区间的集合 A ,其中 A[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠。
cpp复制
1//题目的完整代码 2struct Intervals{ 3 int st, ed; 4} a[100005]; 5 6bool cmp(Intervals x, Intervals y) { 7 return x.ed < y.ed; 8} 9 10class Solution {
举个栗子 给定 N个闭区间 [��ai,��bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。 输出最小组数。
我们可以把所有开始时间和结束时间排序,遇到开始时间就把需要的教室加1,遇到结束时间就把需要的教室减1,在一系列需要的教室个数变化的过程中,峰值就是多同时进行的活动数,也是我们至少需要的教室数。
cpp复制
1//题目的完整代码 2#include
给定 N个闭区间 [ai,bi]以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
分析: 第一步,将所有区间按照左端点从小到大进行排序
第二步,从前往后枚举每个区间,在所有能覆盖start的区间中,选择右端点的最大区间,然后将start更新成右端点的最大值
cpp复制
1//题目的完整代码 2#include
求一个最长序列,使得该序列的任意三个相邻元素,中间的元素是三个中最大的,或者是最小的(最长波浪序列)
cpp复制
1//题目的完整代码 2#include
(最小区间覆盖)给出 n 个区间,第 i 个区间的左右端点是[ai,bi]。现在要在这些区间中选出若干个,使得区间 [0,m] 被所选区间的并覆盖(即每一个 0≤i≤m 都在某个所选的区间中)。保证答案存在,求所选区间个数的最小值。
输入第一行包含两个整数 n 和 m (1≤n≤5000,1≤m≤109) 接下来 n 行,每行两个整数 ai,bi(0≤ai,bi≤m)。
提示:使用贪心法解决这个问题。先用 O(n2)的时间复杂度排序,然后贪心选择这些区间。
试补全程序。
1 #include
2
3 using namespace std;
4
5 const int MAXN = 5000;
6 int n, m;
7 struct segment { int a, b; } A[MAXN];
8
9 void sort() // 排序
10 {
1)①处应填( )
2)②处应填( )
3)③处应填( )
4)④处应填( )
5)⑤处应填( )
1.
A. A[j].b>A[j-1].b
B. A[j].a
C. A[j].a>A[j-1].a
D. A[j].b
2.
A. A[j+1]=A[j];A[j]=t;
B. A[j-1]=A[j];A[j]=t;
C. A[j]=A[j+1];A[j+1]=t;
D. A[j]=A[j-1];A[j-1]=t;
3.
A. A[i].b>A[p-1].b
B. A[i].b
C. A[i].b>A[i-1].b
D. A[i].b
4.
A. q+1
B. q+1
C. q
D. q
5.
A. r=max(r,A[q+1].b)
B. r=max(r,A[q].b)
C. r=max(r,A[q+1].a)
D. q++