Appearance
附录 D · 学习路径与备考规划
面向从零开始备考 GESP 五级的读者,给出知识地图、三阶段路径、8 周计划、自测清单与考场时间分配。 前提:已具备 GESP 四级水平(指针、结构体、函数、递推、三种基础排序、文件与异常)。
一、知识地图(依赖关系的正确顺序)
┌───────────────────────────┐
│ 第9章 算法复杂度分析 │ ← 贯串全局,可随时回看
│ (大O / 多项式 / 对数) │
└─────────────┬─────────────┘
│ 是所有章节的度量工具
┌──────────────────────────┼──────────────────────────┐
│ │ │
┌──▼─────────┐ ┌───────▼────────┐ ┌────────▼───────┐
│ 第2章 数论 │ │ 第6章 递归 │ │ 第3章 链表 │
│ 素数/GCD/ │ │ 基线+递归条件 │ │ 单/双/循环 │
│ 筛法/分解 │ │ 调用栈/记忆化 │ │ 反转/约瑟夫 │
└──┬─────────┘ └───────┬────────┘ └────────────────┘
│ 唯一分解定理 │ 递归是分治的实现手段
│ ┌───────▼────────┐ ┌────────────────┐
│ │ 第7章 分治 │ │ 第4章 高精度 │
│ │ 归并 / 快排 │ │ 加/减/乘/除 │
│ │ 逆序对 / 子段和 │ │ 数组模拟竖式 │
│ └───────┬────────┘ └────────────────┘
│ │ 先有分治,才好理解二分
│ ┌───────▼────────┐
│ │ 第5章 二分 │
│ │ 查找 / 二分答案 │
│ └────────────────┘
│
┌──▼─────────────────────────────────────────────┐
│ 第8章 贪心(相对独立,建议放在最后) │
│ 需先掌握 sort / 结构体排序,部分模型用优先队列 │
└────────────────────────────────────────────────┘推荐阅读顺序:第 1 章(总览)→ 2(数论)→ 6(递归)→ 7(分治)→ 5(二分)→ 3(链表)→ 4(高精度)→ 8(贪心)→ 9(复杂度)。
若时间紧张,也可按目录顺序 2→3→4→5→6→7→8→9 通读,两种顺序均可。
二、三阶段路径
| 阶段 | 目标 | 内容 | 产出 |
|---|---|---|---|
| 第一阶段:建骨架(约 3 周) | 知道"考什么、怎么考" | 通读 8 章正文,不做题,只看讲解与示例 | 每章能说出 3~5 个核心考点 |
| 第二阶段:练手感(约 3 周) | 能把知识变成代码 | 各章「实战练习」全部手写一遍;编程题必须上机 | 模板代码(高精度/二分/归并/快排/贪心)能默写 |
| 第三阶段:补短板(约 2 周) | 消灭盲区 + 提速 | 刷真题;回看附录 B「陷阱 20 条」;用附录 A 查漏 | 错题本 + 考前速查卡 |
三、8 周计划(每周约 6~8 小时)
| 周次 | 精读章节 | 重点任务 | 自检标准 |
|---|---|---|---|
| 第 1 周 | 第 1 章 + 第 2 章(数论 上) | 素数判定、GCD/LCM、模运算 | 能手写 gcd 递归/迭代两种写法 |
| 第 2 周 | 第 2 章(数论 下) | 质因数分解、埃氏筛、线性筛、约数个数与和 | 能默写线性筛(含 break) |
| 第 3 周 | 第 6 章(递归) | 基线/递归条件、调用栈、汉诺塔、记忆化 | 能画出 fib(5) 递归树并说明 O(2ⁿ) 来源 |
| 第 4 周 | 第 7 章(分治) | 归并排序、快速排序、逆序对 | 能默写归并合并函数 + 逆序对计数 |
| 第 5 周 | 第 5 章(二分) | 两种区间写法、lower/upper_bound、二分答案 | 能独立写出木材加工的 check + 二分 |
| 第 6 周 | 第 3 章 + 第 4 章 | 链表反转/约瑟夫;高精度加、减、乘、除 | 能默写高精度加法与 n! 高精度 |
| 第 7 周 | 第 8 章(贪心) | 活动选择、区间选点、合并果子、排队接水、乘船 | 能说出每类模型的排序依据 |
| 第 8 周 | 第 9 章 + 总复习 | 复杂度分析、数据范围反推;回看附录 B | 见「四、自测清单」 |
四、自测清单(第 8 周使用,全打勾即可报名)
选择题 / 判断题(50 分)
- [ ] 说出素数定义,并解释为什么 1 不是素数、2 是唯一偶素数
- [ ] 已知 gcd 求 lcm,且知道"先除后乘"的原因
- [ ] 说出埃氏筛与线性筛的复杂度,并解释 break 的作用
- [ ] 判断"高精度除法需要倒序存储"这类说法的正误并说明理由
- [ ] 区分左闭右闭 / 左闭右开的循环条件与边界更新
- [ ] 说出 lower_bound 与 upper_bound 的判断条件差异
- [ ] 判断"归并稳定 / 快排不稳定"并说明原因
- [ ] 说出快排最坏 O(n²) 的触发条件
- [ ] 判断"贪心总能得到最优解"的正误并给出反例
- [ ] 背出复杂度增长排序,并据 n 的范围反推可用复杂度
编程题(50 分)
- [ ] 默写辗转相除法(递归 + 迭代)
- [ ] 默写线性筛(含
if (i % primes[j] == 0) break) - [ ] 默写高精度加法(含末尾 carry 追加)
- [ ] 默写高精度乘法(高精 × 低精)
- [ ] 默写二分查找(闭区间写法)
- [ ] 默写归并排序 merge 函数
- [ ] 默写单链表反转(三指针)
- [ ] 写出"合并果子"的小根堆解法
- [ ] 写出"活动选择"的完整程序
- [ ] 写出记忆化斐波那契(含 memo 初始化)
上机要求
- [ ] 高精度模板 10 分钟内写完并过样例
- [ ] 二分答案模板 15 分钟内写完并过样例
- [ ] 归并/快排模板 15 分钟内写完并过样例
五、考场时间分配(180 分钟)
| 环节 | 建议时长 | 说明 |
|---|---|---|
| 浏览全卷 | 5 分钟 | 先扫编程题,心里留个底 |
| 单选题(15×2=30) | 20 分钟 | 平均每题 1.3 分钟;卡住的先跳过 |
| 判断题(10×2=20) | 15 分钟 | 注意"必须/所有/任何"等绝对词 |
| 编程题 1(25 分) | 45 分钟 | 通常是数论 / 高精度 / 二分之一 |
| 编程题 2(25 分) | 55 分钟 | 通常是排序衍生 / 贪心 / 递归 |
| 检查与补漏 | 40 分钟 | 回做标记题;检查文件名、输入输出格式 |
⚠️ 三个必须检查的点:
- 输出格式(空格、换行、大小写)——每年都有人因格式丢分
long long是否该用(逆序对计数、合并果子累加、高精度进位)- 数组是否开够(高精度结果开
n+m位;筛法数组开到 n)
六、常见备考误区
| 误区 | 正确做法 |
|---|---|
| 背模板但不理解 | 模板要能"讲出来"——每一步为什么这么写 |
| 只看不写 | 编程题占 50 分,必须上机;手写与默写是两回事 |
| 死磕难题 | 五级先保证基础题全对,再攻压轴 |
| 忽视选择判断 | 选择+判断共 50 分,与编程题等值,性价比极高 |
| 不练复杂度题 | 复杂度是选择题必考点,且是"反推算法"的钥匙 |
用 int 到底 | 养成习惯:计数、累加、n≥1e5 一律先想 long long |
七、四级 → 五级衔接自测(先修知识)
五级默认读者已通过四级。若下面的先修清单有缺口,请先补四级对应章节,否则五级的链表、高精度、分治会很难推进。
7.1 先修清单:四级知识 → 五级用在哪
| 四级知识 | 五级的直接用途 |
|---|---|
| 指针与内存地址 | 链表:struct Node { int val; Node* next; }; 全靠指针串起来 |
| 结构体 | 链表节点、高精度"大整数"结构体、排序题的复合关键字 |
| 二维及多维数组 | 高精度数字位、分治/归并的辅助数组 |
| 函数(值/引用/指针传递) | 递归函数的参数传递;链表必须传指针或引用才能改结构 |
| 递推算法 | 递归的前置思维(递推 = 自底向上,递归 = 自顶向下) |
| 排序算法(冒泡/插入/选择) | 归并排序、快速排序的分治基础;稳定性判断 |
文件操作(freopen) | 评测环境的读写方式;调试时重定向样例 |
| 异常处理 | 读代码题中的 try/catch 流程判断 |
7.2 先修自测(8 题)
自测 1(单选题) int a = 5; int* p = &a; *p = 8; 执行后 a 的值是( ) A. 5 B. 8 C. 地址值 D. 不确定
自测 2(单选题) 已知 Node* p 指向链表中的某个节点,要在 p 之后插入新节点 q,正确的顺序是( ) A. p->next = q; q->next = p->next; B. q->next = p->next; p->next = q; C. q = p->next; p->next = q; D. p->next = q->next; q->next = p;
自测 3(判断题) 数组名是"常量指针",因此 arr++ 这样的写法是非法的。( )
自测 4(判断题) 函数参数采用值传递时,在函数内修改形参会影响实参的值。( )
自测 5(程序阅读题) 下面的函数返回什么?
cpp
int f(int n) {
int a = 1, b = 1;
for (int i = 3; i <= n; i++) {
int c = a + b;
a = b; b = c;
}
return b;
}
// 调用 f(6)自测 6(程序阅读题) 用冒泡排序对数组 {5, 2, 9, 1} 升序排序,共需要比较多少次(按相邻两两比较计)?
自测 7(填空题) 把输入重定向到 data.in、输出重定向到 data.out,两行语句分别是: freopen(______, "r", stdin); 和 freopen(______, "w", stdout);
自测 8(编程题) 输入 n 名学生的姓名与成绩,按成绩降序输出(n ≤ 100,成绩相同按输入顺序)。
🔑 点击查看答案与解析
答案: 1-B 2-B 3-正确 4-错误 5-8 6-6 次 7-"data.in" / "data.out" 8-见下
解析:
- B:
p存的是a的地址,*p就是a本身,*p = 8即a = 8。 - B:先接后面、再接前面——必须先把
q->next指向p原来的后继,再让p->next指向q;写成 A 会丢失后半条链(自环)。这是链表插入的铁律。 - 正确:数组名是常量指针(
int* const),不能自增;要遍历请用下标或另设指针变量int* q = arr;。 - 错误:值传递只是复制一份,改形参不动实参。想改实参要用引用
&或指针*。 - 8:这是递推版斐波那契(1,1,2,3,5,8…),
f(6)= 第 6 项 = 8。注意循环从i=3起,b一直是"当前项"。 - 6 次:n=4 的冒泡需要 n−1=3 趟,比较次数 3+2+1=6 次。(若问"总比较次数上界"则是 n(n−1)/2 = 6。)
freopen("data.in", "r", stdin);与freopen("data.out", "w", stdout);。五级的调试也建议这样重定向样例。- 参考代码(结构体 + 稳定排序):cpp
#include <iostream> #include <string> #include <algorithm> using namespace std; struct Stu { string name; int score; int id; }; int main() { int n; cin >> n; Stu a[105]; for (int i = 0; i < n; i++) { cin >> a[i].name >> a[i].score; a[i].id = i; } for (int i = 0; i < n - 1; i++) // 冒泡:稳定,相等不交换 for (int j = 0; j < n - 1 - i; j++) if (a[j].score < a[j + 1].score) swap(a[j], a[j + 1]); for (int i = 0; i < n; i++) cout << a[i].name << " " << a[i].score << endl; return 0; }📚 拓展 · 超纲:
swap与sort属于更高级别/后续内容;五级若要用sort,需自带比较函数或改用下标排序(考纲五级仅要求手写排序与分治排序)。
八、综合自测卷(两套 · 180 分钟)
📌 使用说明
- 两套卷子均按 GESP 五级真实题型配比:单选题 15×2=30 分、判断题 10×2=20 分、编程题 2×25=50 分,共 100 分。
- 建议严格计时 180 分钟做完一套,中途不查书、不翻笔记;做完再对答案。
- 题目为真题风格改编,非官方真题;编程题请在 IDE 中实际运行验证。
- 判分参考:≥ 85 分可报名;70~85 分建议按错题回看对应章节;< 70 分请重走附录 D 的 8 周计划。
卷一
一、单选题(15 题 × 2 分 = 30 分)
用埃氏筛法求出 1~n 内所有素数的时间复杂度是( ) A. O(n) B. O(n log n) C. O(n log log n) D. O(n²)
gcd(48, 18)的值是( ) A. 2 B. 3 C. 6 D. 12高精度整数用数组存储时通常低位在前(倒序存放),主要原因是( ) A. 便于从低位开始逐位相加与进位 B. 便于输出 C. 节省空间 D. 便于比较大小
已知单链表中某节点的前驱指针
pre,在其后插入一个新节点的时间复杂度是( ) A. O(1) B. O(log n) C. O(n) D. O(n²)在 1~100 的有序表中用二分查找任意一个存在的数,最多需要比较的次数是( ) A. 5 B. 6 C. 7 D. 100
用朴素递归求
fib(n)(fib(n)=fib(n-1)+fib(n-2),无记忆化)的时间复杂度是( ) A. O(n) B. O(n log n) C. O(2ⁿ) D. O(n²)归并排序的时间复杂度与空间复杂度分别是( ) A. O(n log n)、O(1) B. O(n log n)、O(n) C. O(n²)、O(1) D. O(n log n)、O(log n)
经典"活动选择问题"(最多不重叠活动)的正确贪心策略是( ) A. 按开始时间升序选 B. 按结束时间升序选 C. 按持续时间升序选 D. 按活动数量随机选
三重嵌套循环
for(i<n) for(j<n) for(k<n)的时间复杂度是( ) A. O(n) B. O(n²) C. O(n³) D. O(3ⁿ)快速排序在最坏情况下的时间复杂度是( ) A. O(n) B. O(n log n) C. O(n²) D. O(2ⁿ)
1~100 内素数的个数是( ) A. 24 B. 25 C. 26 D. 30
一个 n 位整数与一个 m 位整数相乘,乘积最多是( )位 A. max(n,m) B. n+m−1 C. n+m D. n×m
能用"二分答案"求解的问题,其判定函数必须满足( ) A. 随机性 B. 单调性 C. 对称性 D. 周期性
递归式
T(n) = 2T(n/2) + O(n)的解是( ) A. O(n) B. O(n log n) C. O(n²) D. O(log n)与数组相比,单链表的显著优势是( ) A. 支持随机访问 B. 插入删除不需要移动元素 C. 缓存命中率高 D. 占用空间更小
二、判断题(10 题 × 2 分 = 20 分)
- 1 不是素数,也不是合数。( )
- 任意两个相邻整数互质(最大公约数为 1)。( )
- 二分查找的前提是序列必须有序。( )
- 递归实现一定比循环实现运行更快。( )
- 归并排序是稳定排序。( )
- 贪心算法总能得到全局最优解。( )
- 高精度加法应当从最低位开始逐位相加并处理进位。( )
- 单链表查找第 k 个节点需要 O(k) 时间。( )
- 快速排序平均时间复杂度为 O(n log n),最坏为 O(n²)。( )
- 分治算法要求子问题相互独立;若子问题大量重叠,通常应考虑动态规划。( )
三、编程题(2 题 × 25 分 = 50 分)
编程题 1 · 高精度加法
问题描述: 输入两个非负整数 a、b(位数不超过 200 位),输出 a + b。
输入格式: 两行,第一行 a,第二行 b。 输出格式: 一行,a + b 的结果(不含前导零)。
输入样例:
12345678901234567890
98765432109876543210输出样例:
111111111011111111100编程题 2 · 二分答案:木材切割
问题描述: 有 n 根木头,第 i 根长度为 L[i]。现要把它们切成至少 k 段长度相等的小段(小段长度必须为整数),求小段的最大可能长度;若无法切出 k 段,输出 0。
输入格式: 第一行两个整数 n、k;第二行 n 个整数,表示 L[i]。 输出格式: 一个整数,最大长度。
输入样例:
3 8
10 15 12输出样例:
4(长度为 4 时可切出 2+3+3=8 段;长度为 5 时只有 2+3+2=7 段,不够)
数据范围: 1 ≤ n ≤ 10⁵,1 ≤ k ≤ 10⁹,1 ≤ L[i] ≤ 10⁹。
🔑 卷一答案与解析
单选题: 1-C 2-C 3-A 4-A 5-C 6-C 7-B 8-B 9-C 10-C 11-B 12-C 13-B 14-B 15-B
判断题: 1-√ 2-√ 3-√ 4-× 5-√ 6-× 7-√ 8-√ 9-√ 10-√
解析要点:
- 第 2 题:48 = 18×2 + 12,18 = 12×1 + 6,12 = 6×2 + 0 → gcd = 6。
- 第 5 题:⌈log₂100⌉ = 7(2⁶=64 < 100 ≤ 128=2⁷)。
- 第 6 题:朴素递归 fib 有大量重复子问题,复杂度 O(2ⁿ)(严格为 O(φⁿ)),加记忆化才降为 O(n)。
- 第 11 题:1~100 共 25 个素数(100 以内素数表建议记熟前 10 个:2,3,5,7,11,13,17,19,23,29)。
- 第 12 题:n 位 × m 位,积最多 n+m 位(最少 n+m−1 位),高精度数组要开够。
- 第 4 题:已知前驱时,链表插入只需改两个指针 → O(1);难点在于"找到前驱"要 O(n)。
编程题 1 参考代码:
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string sa, sb;
cin >> sa >> sb;
int a[205] = {0}, b[205] = {0}, c[210] = {0};
int la = sa.size(), lb = sb.size();
for (int i = 0; i < la; i++) a[la - 1 - i] = sa[i] - '0'; // 倒序存放:低位在前
for (int i = 0; i < lb; i++) b[lb - 1 - i] = sb[i] - '0';
int len = max(la, lb);
for (int i = 0; i < len; i++) {
c[i] += a[i] + b[i]; // 逐位相加
c[i + 1] += c[i] / 10; // 进位
c[i] %= 10;
}
if (c[len]) len++; // 最高位进位
while (len > 1 && c[len - 1] == 0) len--; // 去前导零
for (int i = len - 1; i >= 0; i--) cout << c[i];
cout << endl;
return 0;
}编程题 2 参考代码:
cpp
#include <iostream>
using namespace std;
const int N = 100005;
int n;
long long L[N], k;
// 判定:每根木头切成长度 x,能否凑够 k 段
bool ok(long long x) {
if (x == 0) return true;
long long cnt = 0;
for (int i = 0; i < n; i++) {
cnt += L[i] / x;
if (cnt >= k) return true; // 提前退出,防止 long long 溢出
}
return false;
}
int main() {
cin >> n >> k;
long long mx = 0;
for (int i = 0; i < n; i++) { cin >> L[i]; if (L[i] > mx) mx = L[i]; }
long long l = 0, r = mx, ans = 0;
while (l <= r) { // 二分答案:在单调的 ok() 上找最大可行值
long long mid = l + (r - l) / 2;
if (ok(mid)) { ans = mid; l = mid + 1; }
else r = mid - 1;
}
cout << ans << endl;
return 0;
}💡 二分答案模板记住四步:① 确定答案范围
[l, r];② 写判定函数ok(x)(必须单调:x 可行则更小的都可行);③while (l <= r)取中;④ 可行就记录并向右逼近。
卷二
一、单选题(15 题 × 2 分 = 30 分)
关于埃氏筛与线性筛,正确的说法是( ) A. 埃氏筛每个合数只被筛一次 B. 线性筛时间复杂度 O(n),每个合数只被最小质因子筛一次 C. 线性筛比埃氏筛慢 D. 两者复杂度都是 O(n²)
计算
(a * b) % m时,为防止a * b溢出(a、b 可达 10⁹),应( ) A. 直接用intB. 先转成long long再取模 C. 用doubleD. 无法避免高精度减法中,若某一位不够减,应当( ) A. 向高位借 1 当 10 B. 直接输出负数 C. 跳过该位 D. 向低位借位
双向链表删除节点
p(已知其前驱与后继)需要修改的指针数是( ) A. 1 B. 2 C. 3 D. 4二分查找写成
while (l < r)与while (l <= r)的主要区别在于( ) A. 时间复杂度不同 B. 循环终止时l、r的相对位置与是否需要额外判断 C. 前者只能用于降序 D. 后者不能用于整数区间递归深度过大最可能导致的运行时错误是( ) A. 编译错误 B. 栈溢出(Stack Overflow) C. 数组越界 D. 死循环
用分治法求"最大子段和"的时间复杂度是( ) A. O(n) B. O(n log n) C. O(n²) D. O(2ⁿ)
面额为 {1, 5, 10, 25} 的硬币系统中,用"每次取不超过剩余金额的最大面额"找零( ) A. 能得到最少硬币数——该面额系统满足贪心性质 B. 不一定,贪心只对部分金额成立 C. 一定不是最优 D. 无法判断
while (n > 1) { n /= 2; }的循环次数复杂度是( ) A. O(1) B. O(log n) C. O(n) D. O(n log n)用归并排序统计逆序对,其核心依据是( ) A. 逆序对只在左半段 B. 合并时右段元素被提前输出,说明它与左段剩余元素构成逆序对 C. 逆序对等于数组长度 D. 需要额外的哈希表
判断正整数 n(n ≥ 2)是否为素数,试除法只需检查到( ) A. n−1 B. n/2 C. √n D. log n
比较两个高精度正整数的大小,应( ) A. 直接转成
intB. 先比位数,位数相同再从高位到低位比 C. 先比低位 D. 只能逐位相加判断二分查找取中点写成
mid = (l + r) / 2在 l、r 很大时可能( ) A. 超时 B.l + r溢出,应写l + (r - l) / 2C. 死循环 D. 答案错误但一定不溢出对朴素递归
fib(n)加入"记忆化(数组记录已算过的值)"后,复杂度降为( ) A. O(1) B. O(n) C. O(n log n) D. 仍是 O(2ⁿ)枚举一个集合的所有子集,时间复杂度是( ) A. O(n) B. O(n²) C. O(2ⁿ) D. O(n!)
二、判断题(10 题 × 2 分 = 20 分)
- 高精度运算中把低位存数组下标 0,是为了便于进位与末位追加。( )
- 所有分治算法的时间复杂度都是 O(n log n)。( )
- 链表插入/删除节点不需要移动其他元素。( )
- 二分查找要求数据有序且能按下标访问。( )
- 递归函数若没有基线条件(递归出口),会一直调用下去直到栈溢出。( )
- 快速排序是稳定排序。( )
- 使用贪心算法时,需要给出正确性说明或找不到反例。( )
gcd(a, b) = gcd(b, a % b)。( )- 线性筛保证每个合数只被其最小质因子筛掉一次。( )
- 分析时间复杂度时只保留最高阶项,忽略系数与低阶项。( )
三、编程题(2 题 × 25 分 = 50 分)
编程题 1 · 区间素数统计
问题描述: 输入两个整数 L、R,统计闭区间 [L, R] 内素数的个数。
输入格式: 一行两个整数 L、R。 输出格式: 一个整数,素数个数。
输入样例:
10 30输出样例:
6(11, 13, 17, 19, 23, 29 共 6 个)
数据范围: 1 ≤ L ≤ R ≤ 10⁶。
编程题 2 · 贪心:活动安排
问题描述: 有 n 个活动,第 i 个活动的开始时间与结束时间为 (s[i], e[i])。同一时间只能参加一个活动,且活动 [s, e] 与下一个活动的开始时间满足 s ≥ e 即可衔接(即允许首尾相接)。求最多能参加多少个活动。
输入格式: 第一行一个整数 n;接下来 n 行,每行两个整数 s[i]、e[i]。 输出格式: 一个整数,最多活动数。
输入样例:
5
1 3
2 5
3 6
5 7
6 9输出样例:
3(选 [1,3)、[3,6)、[6,9),共 3 个)
数据范围: 1 ≤ n ≤ 10⁵,0 ≤ s[i] < e[i] ≤ 10⁹。
🔑 卷二答案与解析
单选题: 1-B 2-B 3-A 4-B 5-B 6-B 7-B 8-A 9-B 10-B 11-C 12-B 13-B 14-B 15-C
判断题: 1-√ 2-× 3-√ 4-√ 5-√ 6-× 7-√ 8-√ 9-√ 10-√
解析要点:
- 第 3 题:高精度减法不够减时向高位借 1 当 10,同时高位减 1。
- 第 4 题:双向链表删除需改
pre->next与next->pre两个指针(若用前驱指针变量,至少修改 2 处)。 - 第 7 题:分治求最大子段和:左右各 O(n/2) + 合并跨中点 O(n) →
T(n)=2T(n/2)+O(n)= O(n log n)。 - 第 8 题:{1,5,10,25} 这类面额系统贪心成立(答案 A);但换成 {1,3,4} 凑 6,贪心给 4+1+1=3 枚、最优是 3+3=2 枚 → 贪心必须在本题数据下验证后才能用。
- 第 11 题:试除到 √n 即可,因为若 n = a×b,必有 min(a,b) ≤ √n。
- 第 13 题:
l + r可能超过int上限,用l + (r - l) / 2是标准写法。
编程题 1 参考代码(埃氏筛):
cpp
#include <iostream>
using namespace std;
const int MAXN = 1000005;
bool isPrime[MAXN];
int main() {
int L, R;
cin >> L >> R;
for (int i = 2; i <= R; i++) isPrime[i] = true; // 先全部设为素数
for (int i = 2; i * i <= R; i++) // 筛到 √R
if (isPrime[i])
for (int j = i * i; j <= R; j += i) // 从 i*i 开始标记
isPrime[j] = false;
int cnt = 0;
for (int i = L; i <= R; i++)
if (i >= 2 && isPrime[i]) cnt++;
cout << cnt << endl;
return 0;
}编程题 2 参考代码(贪心 · 按结束时间升序):
cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100005;
struct Act { long long s, e; } a[N];
bool cmp(const Act& x, const Act& y) {
if (x.e != y.e) return x.e < y.e; // 结束早的优先
return x.s < y.s;
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i].s >> a[i].e;
sort(a, a + n, cmp); // 📚 拓展 · 超纲:sort 属更高级别内容,五级可用手写排序替代
int ans = 0;
long long last = -1; // 上一个已选活动的结束时间
for (int i = 0; i < n; i++) {
if (a[i].s >= last) { // 能接上就选
ans++;
last = a[i].e;
}
}
cout << ans << endl;
return 0;
}💡 贪心题的通用套路:① 明确"怎么排序"(本题按结束时间);② 明确"选不选"的判定(本题
s ≥ last);③ 用交换论证说服自己:把最优解的第一个活动换成"结束最早的",不会减少可安排的数量。
九、通过之后:向六级衔接
五级之后是六级(树、图、搜索、动态规划起步等更高阶内容)。拿到五级证书后,建议按下面的顺序把"衔接前置能力"先练扎实,避免六级一上来就被数据结构压垮。
9.1 五级 → 六级:能力升级点
| 五级的终点 | 六级的起点 |
|---|---|
| 递归(能写、会画栈) | 递归搜索(DFS/回溯):把"递归"用在遍历所有方案上 |
| 分治(归并、快排) | 树形结构上的分治:二叉树遍历、归并思路迁移 |
| 链表(线性结构的指针操作) | 树与图:多分支的指针/邻接表结构 |
| 贪心(凭直觉找策略) | 动态规划:把"贪心不成立"的题系统化求解 |
| 复杂度(会算 O) | 复杂度(会设计:由数据范围反推算法) |
9.2 衔接清单(六级前必须过关)
- [ ] 能不看模板手写:埃氏筛、归并排序、快速排序、二分查找、高精度加减乘
- [ ] 能用递归写出"枚举所有子集"与"全排列"
- [ ] 能独立写出单链表的增删查改(含头节点、尾插、按值删除)
- [ ] 能把递归调用过程画成栈帧图,并解释栈溢出的原因
- [ ] 看到
n ≤ 20想到指数级(搜索/回溯),n ≤ 10⁵想到 O(n log n),n ≤ 10³想到 O(n²)
9.3 推荐过渡练习
- 把卷一/卷二的 4 道编程题改用不同写法各做一遍(如高精度改成结构体封装、二分答案改成左闭右开写法)。
- 用递归实现"求 1~n 的所有子集"与"n 个数的全排列",体会递归 = 枚举。
- 把归并排序的"合并"过程单独抽出来,手算一遍逆序对的统计过程。
📚 六级涉及树、图、动态规划等内容,超出本书(五级)大纲范围,请转对应的更高等级教材继续学习。
本规划按每周 6~8 小时设计;若每天只能学 30 分钟,请把周期拉长到 12~16 周。
