贪心法
贪心法
贪心策略的适用条件与常见模型。
概念
贪心法在每一步都选当前看起来最优的决策,且不再回头修改。能正确的前提通常是「局部最优能推出全局最优」——CSP-J 层面不必严格证明,但要能识别常见模型:按某个关键字排序后依次处理、选最大/最小、能放就放等。与 cspj.algo.dp-basic 区分:DP 要比较多种子方案取最优,贪心只保留一条决策链;与 cspj.algo.enumerate-simulate 区分:贪心不穷举全部方案。与 cspj.algo.sorting 的关系:很多贪心第一步是排序,但排序叶子考的是算法本身,贪心叶子考的是「为什么这样排之后一路选就对」。
CSP-J 常考
cspj-r1-2019第 11 题:单选题判断贪心策略或选取顺序是否合理。cspj-r1-2020第 41–43 题:完善程序,按贪心规则补全比较或选择逻辑。cspj-r1-2025第 41 题:完善程序,与区间或序列上的贪心选取相关。cspj-r2-2023第 2 题:复赛编程,维护前方最低油价,只在必要时加油。cspj-r2-2025第 1 题:复赛编程,抽取数字后按降序拼接得最大整数。
// 活动选择:按结束时间升序,能选就选
struct Act { int s, e; };
bool cmp(Act a, Act b) { return a.e < b.e; }
// 排序后遍历,维护 lastEnd,若 a.s >= lastEnd 则选中
易错点
- 排序关键字选错:例如「按起点排序」与「按终点排序」在贪心题里结论可能完全不同。
- 完善程序里
if条件写反,导致「能选时跳过、不能选时选入」。 - 把贪心硬套在需要回溯或 DP 的题上;初赛单选若问「一定正确吗」,要能举反例或说明适用条件。
新学期开学了,小胖想减肥,健身教练给小胖制定了两个训练方案。
- 方案一:每次连续跑 3 公里可以消耗 300 千卡(耗时半小时);
- 方案二:每次连续跑 5 公里可以消耗 600 千卡(耗时 1 小时)。
小胖每周周一到周四能抽出半小时跑步,周五到周日能抽出一小时跑步。
另外,教练建议小胖每周最多跑21公里,否则会损伤膝盖。
请问如果小胖想严格执行教练的训练方案,并且不想损伤膝盖,每周最多通过跑步消耗多少千卡?()
整组作答后提交批改,成绩计入掌握度 · 已答 0/1
(最小区间覆盖)给出 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) 的时间复杂度排序,然后贪心选择这些区间。
试补全程序。
#include <iostream>
using namespace std;
const int MAXN = 5000;
int n, m;
struct segment { int a, b; } A[MAXN];
void sort() // 排序
{
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j++)
if (①)
{
segment t = A[j];
②
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> A[i].a >> A[i].b;
sort();
int p = 1;
for (int i = 1; i < n; i++)
if (③)
A[p++] = A[i];
n = p;
int ans = 0, r = 0;
int q = 0;
while (r < m)
{
while (④)
q++;
⑤;
ans++;
}
cout << ans << endl;
return 0;
}
#include <iostream>23using namespace std;45const int MAXN = 5000;6int n, m;7struct segment { int a, b; } A[MAXN];89void sort() // 排序10{11 for (int i = 0; i < n; i++)12 for (int j = 1; j < n; j++)13 if (①)14 {15 segment t = A[j];16 ②17 }18}1920int main()21{22 cin >> n >> m;23 for (int i = 0; i < n; i++)24 cin >> A[i].a >> A[i].b;25 sort();26 int p = 1;27 for (int i = 1; i < n; i++)28 if (③)29 A[p++] = A[i];30 n = p;31 int ans = 0, r = 0;32 int q = 0;33 while (r < m)34 {35 while (④)36 q++;37 ⑤;38 ans++;39 }40 cout << ans << endl;41 return 0;42}原文保真说明:来源改写 1 处(照原卷保留,未作修改)
来源题面被改写其它程序 · 第 24 行
原卷作
cin >> A[i].a >> A[i].b;,常规写法应为cin >> A[i].a >> A[i].b;洛谷抓取件为 A[i]・b(中点 U+30FB);pages/pg-09.png 第 24 行为 .b。另去除洛谷多余行首缩进、ans=0 空格按图像统一。
已按官方原件恢复:现为原卷原文
- 41(③)单选空位 ③3 分
③处应填( )
整组作答后提交批改,成绩计入掌握度 · 已答 0/1
(最小区间覆盖)给出 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) 的时间复杂度排序,然后贪心选择这些区间。
试补全程序。
#include <iostream>
using namespace std;
const int MAXN = 5000;
int n, m;
struct segment { int a, b; } A[MAXN];
void sort() // 排序
{
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j++)
if (①)
{
segment t = A[j];
②
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> A[i].a >> A[i].b;
sort();
int p = 1;
for (int i = 1; i < n; i++)
if (③)
A[p++] = A[i];
n = p;
int ans = 0, r = 0;
int q = 0;
while (r < m)
{
while (④)
q++;
⑤;
ans++;
}
cout << ans << endl;
return 0;
}
#include <iostream>23using namespace std;45const int MAXN = 5000;6int n, m;7struct segment { int a, b; } A[MAXN];89void sort() // 排序10{11 for (int i = 0; i < n; i++)12 for (int j = 1; j < n; j++)13 if (①)14 {15 segment t = A[j];16 ②17 }18}1920int main()21{22 cin >> n >> m;23 for (int i = 0; i < n; i++)24 cin >> A[i].a >> A[i].b;25 sort();26 int p = 1;27 for (int i = 1; i < n; i++)28 if (③)29 A[p++] = A[i];30 n = p;31 int ans = 0, r = 0;32 int q = 0;33 while (r < m)34 {35 while (④)36 q++;37 ⑤;38 ans++;39 }40 cout << ans << endl;41 return 0;42}原文保真说明:来源改写 1 处(照原卷保留,未作修改)
来源题面被改写其它程序 · 第 24 行
原卷作
cin >> A[i].a >> A[i].b;,常规写法应为cin >> A[i].a >> A[i].b;洛谷抓取件为 A[i]・b(中点 U+30FB);pages/pg-09.png 第 24 行为 .b。另去除洛谷多余行首缩进、ans=0 空格按图像统一。
已按官方原件恢复:现为原卷原文
- 42(④)单选空位 ④3 分
④处应填( )
整组作答后提交批改,成绩计入掌握度 · 已答 0/1
(最小区间覆盖)给出 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) 的时间复杂度排序,然后贪心选择这些区间。
试补全程序。
#include <iostream>
using namespace std;
const int MAXN = 5000;
int n, m;
struct segment { int a, b; } A[MAXN];
void sort() // 排序
{
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j++)
if (①)
{
segment t = A[j];
②
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> A[i].a >> A[i].b;
sort();
int p = 1;
for (int i = 1; i < n; i++)
if (③)
A[p++] = A[i];
n = p;
int ans = 0, r = 0;
int q = 0;
while (r < m)
{
while (④)
q++;
⑤;
ans++;
}
cout << ans << endl;
return 0;
}
#include <iostream>23using namespace std;45const int MAXN = 5000;6int n, m;7struct segment { int a, b; } A[MAXN];89void sort() // 排序10{11 for (int i = 0; i < n; i++)12 for (int j = 1; j < n; j++)13 if (①)14 {15 segment t = A[j];16 ②17 }18}1920int main()21{22 cin >> n >> m;23 for (int i = 0; i < n; i++)24 cin >> A[i].a >> A[i].b;25 sort();26 int p = 1;27 for (int i = 1; i < n; i++)28 if (③)29 A[p++] = A[i];30 n = p;31 int ans = 0, r = 0;32 int q = 0;33 while (r < m)34 {35 while (④)36 q++;37 ⑤;38 ans++;39 }40 cout << ans << endl;41 return 0;42}原文保真说明:来源改写 1 处(照原卷保留,未作修改)
来源题面被改写其它程序 · 第 24 行
原卷作
cin >> A[i].a >> A[i].b;,常规写法应为cin >> A[i].a >> A[i].b;洛谷抓取件为 A[i]・b(中点 U+30FB);pages/pg-09.png 第 24 行为 .b。另去除洛谷多余行首缩进、ans=0 空格按图像统一。
已按官方原件恢复:现为原卷原文
- 43(⑤)单选空位 ⑤3 分
⑤处应填( )
在数据压缩编码中的哈夫曼编码方法,在本质上是一种( )的策略。
有四个人要从 A 点坐一条船过河到 B 点,船一开始在 A 点。该船一次最多可坐两个人。 已知这四个人中每个人独自坐船的过河时间分别为 1,2,4,8,且两个人坐船的过河时间为两人独自过河时间的较大者。则最短( )时间可以让四个人都过河到 B 点(包括从 B 点把船开回 A 点的时间)。
公路
C++时间 1 s内存 512 MB 文件 IO:road.in → road.out
小苞准备开着车沿着公路自驾。
公路上一共有 n 个站点,编号为从 1 到 n。其中站点 i 与站点 i+1 的距离为 vi 公里。
公路上每个站点都可以加油,编号为 i 的站点一升油的价格为 ai 元,且每个站点只出售整数升的油。
小苞想从站点 1 开车到站点 n,一开始小苞在站点 1 且车的油箱是空的。已知车的油箱足够大,可以装下任意多的油,且每升油可以让车前进 d 公里。问小苞从站点 1 开到站点 n,至少要花多少钱加油?
输入格式
从文件 road.in 中读入数据。
输入的第一行包含两个正整数 n 和 d,分别表示公路上站点的数量和车每升油可以前进的距离。
输入的第二行包含 n−1 个正整数 v1,v2…vn−1,分别表示站点间的距离。
输入的第三行包含 n 个正整数 a1,a2…an,分别表示在不同站点加油的价格。
输出格式
输出到文件 road.out 中。
输出一行,仅包含一个正整数,表示从站点 1 开到站点 n,小苞至少要花多少钱加油。
样例
5 4 10 10 10 10 9 8 9 6 5
79
最优方案下:小苞在站点 1 买了 3 升油,在站点 2 购买了 5 升油,在站点 4 购买了 2 升油。
数据范围
对于所有测试数据保证:1≤n≤105,1≤d≤105,1≤vi≤105,1≤ai≤105。
| 测试点 | n≤ | 特殊性质 |
|---|---|---|
| 1∼5 | 8 | 无 |
| 6∼10 | 103 | 无 |
| 11∼13 | 105 | A |
| 14∼16 | 105 | B |
| 17∼20 | 105 | 无 |
特殊性质 A:站点 1 的油价最低。
特殊性质 B:对于所有 1≤i<n,vi 为 d 的倍数。
部分分
- 25 分
测试点 1∼5:n≤8,无特殊性质。
- 25 分
测试点 6∼10:n≤103,无特殊性质。
- 15 分
测试点 11∼13:n≤105,特殊性质 A。
- 15 分
测试点 14∼16:n≤105,特殊性质 B。
- 20 分
测试点 17∼20:n≤105,无特殊性质。
原文保真说明:来源改写 1 处(照原卷保留,未作修改)
来源题面被改写其它数据范围
原卷作
| $14 \sim 16$ | $10^5$ | B |洛谷 P9749 测试点表第 14~16 档 $n$ 列写 ^(继承上一档 $10^3$);官方 PDF 第 5 页图像为准:三档均为 $n \le 10^5$、特殊性质 B。
已按官方原件恢复:现为原卷原文
你的代码(C++)
提交后由评测机编译运行,结果见下方76 B / 64 KB
判题结果
整组作答后提交批改,成绩计入掌握度 · 已答 0/1
(2)(精明与糊涂)有 N 个人,分为两类:
i) 精明人:永远能正确判断其他人是精明还是糊涂;
ii)糊涂人:判断不可靠,会给出随机的判断。
已知精明人严格占据多数,即如果精明人有 k 个,则满足 k>N/2。
你只能通过函数 query(i,j) 让第 i 个人判断第 j 个人:返回 true 表示判断结果为“精明人”;返回 false 表示判断结果为“糊涂人”。你的目标是,通过这些互相判断,找出至少一个百分之百确定的精明人。同时,你无需关心 query(i,j) 的内部实现。
以下程序利用“精明人占多数”的优势。设想一个“消除”的过程,让人们互相判断并进行抵消。经过若干轮抵消后,最终留下的候选人必然属于多数派,即精明人。
例如,假设有三人 0,1,2。如果 0 说 1 是糊涂人,而 1 也说 0 是糊涂人,则 0 和 1 至少有一个是糊涂人。程序将同时淘汰 0 和 1。由于三人里至少有两个精明人,我们确定 2 是精明人。
试补全程序。
#include <iostream>2#include <vector>3using namespace std;45int N;6bool query(int i, int j);78int main() {9 cin >> N;1011 int candidate = 0;12 int count = __①__;13 for (int i = 1; i < N; ++i) {14 if (__②__) {15 candidate = i;16 count = 1;17 } else {18 if (__③__) {19 __④__;20 } else {21 count++;22 }23 }24 }25 cout << __⑤__ << endl;26 return 0;27}- 41(④)单选空位 ④3 分
④处应填( )
拼数
C++时间 1 s内存 512 MB 文件 IO:number.in → number.out
小 R 正在学习字符串处理。小 X 给了小 R 一个字符串 s,其中 s 仅包含小写英文字母及数字,且包含至少一个 1∼9 中的数字。小 X 希望小 R 使用 s 中的任意多个数字,按任意顺序拼成一个正整数。注意:小 R 可以选择 s 中相同的数字,但每个数字只能使用一次。例如,若 s 为 1a01b,则小 R 可以同时选择第 1,3,4 个字符,分别为 1,0,1,拼成正整数 101 或 110;但小 R 不能拼成正整数 111,因为 s 仅包含两个数字 1。
小 R 想知道,在他所有能拼成的正整数中,最大的是多少。你需要帮助小 R 求出他能拼成的正整数的最大值。
输入格式
从文件 number.in 中读入数据。
输入的第一行包含一个字符串 s,表示小 X 给小 R 的字符串。
输出格式
输出到文件 number.out 中。
输出一行一个正整数,表示小 R 能拼成的正整数的最大值。
样例
5
5
s 仅包含一个数字 5,因此小 R 仅能拼成正整数 5。
290es1q0
92100
s 包含数字 2,9,0,1,0。可以证明,小 R 拼成的正整数的最大值为 92100。
数据范围
设 ∣s∣ 为字符串 s 的长度。对于所有测试数据,保证:
- 1≤∣s∣≤106;
- s 仅包含小写英文字母及数字,且包含至少一个 1∼9 中的数字。
| 测试点编号 | ∣s∣≤ | 特殊性质 |
|---|---|---|
| 1 | 1 | A |
| 2 | 2 | A |
| 3 | 2 | 无 |
| 4 | 10 | A |
| 5,6 | 10 | 无 |
| 7,8 | 102 | A |
| 9∼11 | 102 | 无 |
| 12 | 103 | A |
| 13,14 | 103 | 无 |
| 15 | 105 | A |
| 16,17 | 105 | B |
| 18,19 | 105 | 无 |
| 20 | 106 | A |
| 21,22 | 106 | B |
| 23∼25 | 106 | 无 |
特殊性质 A:s 仅包含数字。
特殊性质 B:s 仅包含不超过 103 个数字。
部分分
- 4 分
测试点 1:∣s∣≤1,特殊性质 A。
- 4 分
测试点 2:∣s∣≤2,特殊性质 A。
- 4 分
测试点 3:∣s∣≤2,无特殊性质。
- 4 分
测试点 4:∣s∣≤10,特殊性质 A。
- 8 分
测试点 5,6:∣s∣≤10,无特殊性质。
- 8 分
测试点 7,8:∣s∣≤102,特殊性质 A。
- 12 分
测试点 9∼11:∣s∣≤102,无特殊性质。
- 4 分
测试点 12:∣s∣≤103,特殊性质 A。
- 8 分
测试点 13,14:∣s∣≤103,无特殊性质。
- 4 分
测试点 15:∣s∣≤105,特殊性质 A。
- 8 分
测试点 16,17:∣s∣≤105,特殊性质 B。
- 8 分
测试点 18,19:∣s∣≤105,无特殊性质。
- 4 分
测试点 20:∣s∣≤106,特殊性质 A。
- 8 分
测试点 21,22:∣s∣≤106,特殊性质 B。
- 12 分
测试点 23∼25:∣s∣≤106,无特殊性质。
你的代码(C++)
提交后由评测机编译运行,结果见下方76 B / 64 KB