Skip to content

附录 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 分钟回做标记题;检查文件名、输入输出格式

⚠️ 三个必须检查的点:

  1. 输出格式(空格、换行、大小写)——每年都有人因格式丢分
  2. long long 是否该用(逆序对计数、合并果子累加、高精度进位)
  3. 数组是否开够(高精度结果开 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-见下

解析:

  1. B:p 存的是 a 的地址,*p 就是 a 本身,*p = 8 即 a = 8。
  2. B:先接后面、再接前面——必须先把 q->next 指向 p 原来的后继,再让 p->next 指向 q;写成 A 会丢失后半条链(自环)。这是链表插入的铁律。
  3. 正确:数组名是常量指针(int* const),不能自增;要遍历请用下标或另设指针变量 int* q = arr;。
  4. 错误:值传递只是复制一份,改形参不动实参。想改实参要用引用 & 或指针 *。
  5. 8:这是递推版斐波那契(1,1,2,3,5,8…),f(6) = 第 6 项 = 8。注意循环从 i=3 起,b 一直是"当前项"。
  6. 6 次:n=4 的冒泡需要 n−1=3 趟,比较次数 3+2+1=6 次。(若问"总比较次数上界"则是 n(n−1)/2 = 6。)
  7. freopen("data.in", "r", stdin); 与 freopen("data.out", "w", stdout);。五级的调试也建议这样重定向样例。
  8. 参考代码(结构体 + 稳定排序):
    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. 用埃氏筛法求出 1~n 内所有素数的时间复杂度是( ) A. O(n) B. O(n log n) C. O(n log log n) D. O(n²)

  2. gcd(48, 18) 的值是( ) A. 2 B. 3 C. 6 D. 12

  3. 高精度整数用数组存储时通常低位在前(倒序存放),主要原因是( ) A. 便于从低位开始逐位相加与进位 B. 便于输出 C. 节省空间 D. 便于比较大小

  4. 已知单链表中某节点的前驱指针 pre,在其后插入一个新节点的时间复杂度是( ) A. O(1) B. O(log n) C. O(n) D. O(n²)

  5. 在 1~100 的有序表中用二分查找任意一个存在的数,最多需要比较的次数是( ) A. 5 B. 6 C. 7 D. 100

  6. 用朴素递归求 fib(n)(fib(n)=fib(n-1)+fib(n-2),无记忆化)的时间复杂度是( ) A. O(n) B. O(n log n) C. O(2ⁿ) D. O(n²)

  7. 归并排序的时间复杂度与空间复杂度分别是( ) 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)

  8. 经典"活动选择问题"(最多不重叠活动)的正确贪心策略是( ) A. 按开始时间升序选 B. 按结束时间升序选 C. 按持续时间升序选 D. 按活动数量随机选

  9. 三重嵌套循环 for(i<n) for(j<n) for(k<n) 的时间复杂度是( ) A. O(n) B. O(n²) C. O(n³) D. O(3ⁿ)

  10. 快速排序在最坏情况下的时间复杂度是( ) A. O(n) B. O(n log n) C. O(n²) D. O(2ⁿ)

  11. 1~100 内素数的个数是( ) A. 24 B. 25 C. 26 D. 30

  12. 一个 n 位整数与一个 m 位整数相乘,乘积最多是( )位 A. max(n,m) B. n+m−1 C. n+m D. n×m

  13. 能用"二分答案"求解的问题,其判定函数必须满足( ) A. 随机性 B. 单调性 C. 对称性 D. 周期性

  14. 递归式 T(n) = 2T(n/2) + O(n) 的解是( ) A. O(n) B. O(n log n) C. O(n²) D. O(log n)

  15. 与数组相比,单链表的显著优势是( ) A. 支持随机访问 B. 插入删除不需要移动元素 C. 缓存命中率高 D. 占用空间更小

二、判断题(10 题 × 2 分 = 20 分) ​

  1. 1 不是素数,也不是合数。( )
  2. 任意两个相邻整数互质(最大公约数为 1)。( )
  3. 二分查找的前提是序列必须有序。( )
  4. 递归实现一定比循环实现运行更快。( )
  5. 归并排序是稳定排序。( )
  6. 贪心算法总能得到全局最优解。( )
  7. 高精度加法应当从最低位开始逐位相加并处理进位。( )
  8. 单链表查找第 k 个节点需要 O(k) 时间。( )
  9. 快速排序平均时间复杂度为 O(n log n),最坏为 O(n²)。( )
  10. 分治算法要求子问题相互独立;若子问题大量重叠,通常应考虑动态规划。( )

三、编程题(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 分) ​

  1. 关于埃氏筛与线性筛,正确的说法是( ) A. 埃氏筛每个合数只被筛一次 B. 线性筛时间复杂度 O(n),每个合数只被最小质因子筛一次 C. 线性筛比埃氏筛慢 D. 两者复杂度都是 O(n²)

  2. 计算 (a * b) % m 时,为防止 a * b 溢出(a、b 可达 10⁹),应( ) A. 直接用 int B. 先转成 long long 再取模 C. 用 double D. 无法避免

  3. 高精度减法中,若某一位不够减,应当( ) A. 向高位借 1 当 10 B. 直接输出负数 C. 跳过该位 D. 向低位借位

  4. 双向链表删除节点 p(已知其前驱与后继)需要修改的指针数是( ) A. 1 B. 2 C. 3 D. 4

  5. 二分查找写成 while (l < r) 与 while (l <= r) 的主要区别在于( ) A. 时间复杂度不同 B. 循环终止时 l、r 的相对位置与是否需要额外判断 C. 前者只能用于降序 D. 后者不能用于整数区间

  6. 递归深度过大最可能导致的运行时错误是( ) A. 编译错误 B. 栈溢出(Stack Overflow) C. 数组越界 D. 死循环

  7. 用分治法求"最大子段和"的时间复杂度是( ) A. O(n) B. O(n log n) C. O(n²) D. O(2ⁿ)

  8. 面额为 {1, 5, 10, 25} 的硬币系统中,用"每次取不超过剩余金额的最大面额"找零( ) A. 能得到最少硬币数——该面额系统满足贪心性质 B. 不一定,贪心只对部分金额成立 C. 一定不是最优 D. 无法判断

  9. while (n > 1) { n /= 2; } 的循环次数复杂度是( ) A. O(1) B. O(log n) C. O(n) D. O(n log n)

  10. 用归并排序统计逆序对,其核心依据是( ) A. 逆序对只在左半段 B. 合并时右段元素被提前输出,说明它与左段剩余元素构成逆序对 C. 逆序对等于数组长度 D. 需要额外的哈希表

  11. 判断正整数 n(n ≥ 2)是否为素数,试除法只需检查到( ) A. n−1 B. n/2 C. √n D. log n

  12. 比较两个高精度正整数的大小,应( ) A. 直接转成 int B. 先比位数,位数相同再从高位到低位比 C. 先比低位 D. 只能逐位相加判断

  13. 二分查找取中点写成 mid = (l + r) / 2 在 l、r 很大时可能( ) A. 超时 B. l + r 溢出,应写 l + (r - l) / 2 C. 死循环 D. 答案错误但一定不溢出

  14. 对朴素递归 fib(n) 加入"记忆化(数组记录已算过的值)"后,复杂度降为( ) A. O(1) B. O(n) C. O(n log n) D. 仍是 O(2ⁿ)

  15. 枚举一个集合的所有子集,时间复杂度是( ) A. O(n) B. O(n²) C. O(2ⁿ) D. O(n!)

二、判断题(10 题 × 2 分 = 20 分) ​

  1. 高精度运算中把低位存数组下标 0,是为了便于进位与末位追加。( )
  2. 所有分治算法的时间复杂度都是 O(n log n)。( )
  3. 链表插入/删除节点不需要移动其他元素。( )
  4. 二分查找要求数据有序且能按下标访问。( )
  5. 递归函数若没有基线条件(递归出口),会一直调用下去直到栈溢出。( )
  6. 快速排序是稳定排序。( )
  7. 使用贪心算法时,需要给出正确性说明或找不到反例。( )
  8. gcd(a, b) = gcd(b, a % b)。( )
  9. 线性筛保证每个合数只被其最小质因子筛掉一次。( )
  10. 分析时间复杂度时只保留最高阶项,忽略系数与低阶项。( )

三、编程题(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 推荐过渡练习 ​

  1. 把卷一/卷二的 4 道编程题改用不同写法各做一遍(如高精度改成结构体封装、二分答案改成左闭右开写法)。
  2. 用递归实现"求 1~n 的所有子集"与"n 个数的全排列",体会递归 = 枚举。
  3. 把归并排序的"合并"过程单独抽出来,手算一遍逆序对的统计过程。

📚 六级涉及树、图、动态规划等内容,超出本书(五级)大纲范围,请转对应的更高等级教材继续学习。


本规划按每周 6~8 小时设计;若每天只能学 30 分钟,请把周期拉长到 12~16 周。

百炼成钢,融会贯通