> For the complete documentation index, see [llms.txt](https://docs.1l.lol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.1l.lol/gesp-zone/gesp/gesp-5.md).

# GESP C++ 五级 · 复习资料

```
依据 CCF GESP 官方认证大纲整理 · 数论 / 高精度 / 链表 / 核心算法思想

  初等数论
  高精度运算
  链表
  二分算法
  递归
  分治
  贪心
  复杂度分析

180 分钟
```

考试时长（5-8 级）

```
15×2 分
```

单选题 15 道

```
10×2 分
```

判断题 10 道

```
2×25 分
```

编程题 2 道

```
100 分
```

总分

```
编程题占一半分值，是决定成败的关键；客观题（单选+判断）共 50 分，是保底的基本盘。
```

## 📖 目录（官方八大知识块）

1. 初等数论
2. C++ 高精度运算
3. 链表（单 / 双 / 循环）
4. 二分查找与二分答案
5. 递归算法
6. 分治算法（归并 / 快排）
7. 贪心算法
8. 算法复杂度估算
9. 附录：备考策略与真题资源

   1

## 初等数论

```
  高频重点

  考核目标：掌握素数与合数、最大公约数与最小公倍数、同余与模运算、约数与倍数、质因数分解、奇偶性等概念；熟练使用辗转相除法、埃氏筛法、线性筛法和唯一分解定理解决问题。
```

### 1.1 基本概念（选择题常考）

| 概念     | 要点                                                 |
| ------ | -------------------------------------------------- |
| 素数（质数） | 大于 1 且只有 1 和它本身两个因数。1 不是素数，2 是唯一的偶素数。              |
| 合数     | 大于 1 且除了 1 和本身还有其他因数。1 也不是合数。                      |
| 约数与倍数  | 若 a \| b（a 整除 b），则 a 是 b 的约数，b 是 a 的倍数。            |
| 同余     | a ≡ b (mod m) 表示 a、b 除以 m 的余数相同，即 (a − b) 能被 m 整除。 |
| 奇偶性    | 奇±奇=偶，偶±偶=偶，奇±偶=奇；奇×奇=奇，其余乘积为偶。常用于快速排除选项。          |

### 1.2 辗转相除法（欧几里得算法）求 GCD

```
核心原理：`gcd(a, b) = gcd(b, a mod b)`，当余数为 0 时，除数即为最大公约数。
```

```cpp
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);   // 递归写法，最常考
}

// 迭代写法
int gcd(int a, int b) {
    while (b != 0) { int r = a % b; a = b; b = r; }
    return a;
}
```

```
**最小公倍数**：`lcm(a, b) = a / gcd(a, b) * b`

先除后乘 `a / gcd(a,b) * b`，防止 `a * b` 溢出！这是判断题最爱考的细节之一。另外 gcd(a, 0) = a，注意 0 的边界情况。

更相减损法（大数减小数直到相等）是辗转相除的"减法版"，复杂度较差 O(减法次数较多)，选择题会对比两者效率。
```

### 1.3 素数判定（试除法）

```cpp
bool isPrime(long long n) {
    if (n < 2) return false;          // 0、1 都不是素数
    for (long long i = 2; i * i <= n; i++) // 只试除到 √n
        if (n % i == 0) return false;
    return true;
}
```

```
循环条件写 `i * i <= n` 而不是 `i <= sqrt(n)`（浮点精度问题）；i 要用 long long，否则 i*i 可能溢出。
```

### 1.4 埃氏筛法（埃拉托斯特尼筛）

```
思想：从 2 开始，把每个素数的所有倍数标记为合数；未被标记的就是素数。时间复杂度 O(n log log n)。
```

```cpp
const int N = 1e6 + 5;
bool notPrime[N];               // true 表示合数

void sieve(int n) {
    for (int i = 2; i <= n; i++) {
        if (!notPrime[i]) {         // i 是素数
            for (long long j = (long long)i * i; j <= n; j += i)
                notPrime[j] = true;  // 从 i*i 开始筛即可
        }
    }
}
```

### 1.5 线性筛法（欧拉筛）

```
埃氏筛中一个合数（如 12 = 2×6 = 3×4）会被重复标记。线性筛保证**每个合数只被其最小质因子筛掉一次**，复杂度 O(n)。
```

```cpp
int primes[N], cnt = 0;
bool vis[N];

void linearSieve(int n) {
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) primes[cnt++] = i;      // 记录素数
        for (int j = 0; j < cnt && (long long)i * primes[j] <= n; j++) {
            vis[i * primes[j]] = true;
            if (i % primes[j] == 0) break;  // 关键：保证只被最小质因子筛
        }
    }
}
```

| 筛法  | 时间复杂度          | 核心区别                     |
| --- | -------------- | ------------------------ |
| 埃氏筛 | O(n log log n) | 用每个素数筛掉其倍数，合数可能被重复标记     |
| 线性筛 | O(n)           | 每个合数只被最小质因子标记一次，需要 break |

### 1.6 唯一分解定理（算术基本定理）

```
**任意大于 1 的正整数都能唯一地分解为若干质数的乘积**：N = p₁^a₁ × p₂^a₂ × … × pₖ^aₖ（p 为质数）。
```

```cpp
// 质因数分解：例如 60 = 2^2 * 3 * 5
void factorize(int n) {
    for (int i = 2; (long long)i * i <= n; i++) {
        int c = 0;
        while (n % i == 0) { n /= i; c++; }
        if (c > 0) cout << i << "^" << c << " ";
    }
    if (n > 1) cout << n << "^1";  // 剩下的大于1的部分也是质因子
}
```

```
由唯一分解定理可推：**约数个数** = (a₁+1)(a₂+1)…(aₖ+1)。选择题常考"360 有多少个约数"这类题：360 = 2³×3²×5¹ → 4×3×2 = 24 个。

练习：将 2026 做质因数分解（2026 = 2 × 1013，其中 1013 是素数）；求 gcd(2026, 1000) 与 lcm(2026, 1000)。

  2
```

## C++ 高精度运算（数组模拟）

```
  编程题大户

  考核目标：当数据超出 long long（约 9.2×10¹⁸）范围时，用数组或字符串模拟大整数的加、减、乘、除法。
```

### 2.1 通用套路（必须背熟）

1. 读入用字符串：string s; cin >> s;
2. 逆序存入数组（低位在前，方便进位对齐）：a\[i] = s\[s.size()-1-i] - '0'
3. 逐位运算 + 处理进位/借位
4. 去除前导零，逆序输出

### 2.2 高精度加法

```cpp
string add(string a, string b) {
    if (a.size() < b.size()) swap(a, b);   // 保证 a 更长
    vector<int> A, B, C;
    for (int i = a.size()-1; i >= 0; i--) A.push_back(a[i]-'0');
    for (int i = b.size()-1; i >= 0; i--) B.push_back(b[i]-'0');
    int carry = 0;
    for (size_t i = 0; i < A.size(); i++) {
        int t = A[i] + (i < B.size() ? B[i] : 0) + carry;
        C.push_back(t % 10); carry = t / 10;
    }
    if (carry) C.push_back(carry);
    string res;
    for (int i = C.size()-1; i >= 0; i--) res += C[i] + '0';
    return res;
}
```

### 2.3 高精度减法

```
先比较大小：若 a < b，交换并输出负号。逐位借位：
```

```cpp
// 前提：A >= B（已保证），均为逆序数组
vector<int> sub(vector<int> A, vector<int> B) {
    vector<int> C;
    for (int i = 0, borrow = 0; i < (int)A.size(); i++) {
        int t = A[i] - borrow - (i < (int)B.size() ? B[i] : 0);
        if (t < 0) { t += 10; borrow = 1; } // 借位
        else borrow = 0;
        C.push_back(t);
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back(); // 去前导零
    return C;
}
```

```
减法三大坑：① 忘记判断大小导致结果为负；② 借位后忘记 +10；③ 忘记去前导零（"007" 应输出 "7"）。
```

### 2.4 高精度乘法

```
**高精 × 低精**（最常考）：整体乘一个小整数，逐位乘 + 进位。
```

```cpp
vector<int> mul(vector<int> A, int b) {
    vector<int> C;
    for (int i = 0, carry = 0; i < (int)A.size() || carry; i++) {
        int t = carry;
        if (i < (int)A.size()) t += A[i] * b;
        C.push_back(t % 10); carry = t / 10;
    }
    while (C.size() > 1 && C.back() == 0) C.pop_back(); // b=0 时
    return C;
}
```

```
**高精 × 高精**：类似竖式，`C[i+j] += A[i]*B[j]`，最后统一处理进位。注意结果最大位数约为两数位数之和。
```

### 2.5 高精度除法（高精 ÷ 低精）

```
从**高位往低位** 除（与其他三种相反！），余数逐位传递：
```

```cpp
vector<int> div(vector<int> A, int b, int &r) {  // r 带出余数
    vector<int> C;
    r = 0;
    for (int i = A.size()-1; i >= 0; i--) {  // 高位到低位
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());                          // 转回逆序存储
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}
```

```
判断题常考："高精度运算中四种运算的循环方向都一样"——**错**，除法从高位往低位算，其余从低位往高位算。

练习：计算 2^100 的精确值（高精度乘法连乘）；计算 100! 的值；大数斐波那契数列的第 500 项。

  3
```

## 链表：单链表 / 双链表 / 循环链表

```
  客观题高频

  考核目标：掌握三种链表的创建、插入、删除、遍历、查找、反转操作；理解链表与数组的性能差异。
```

### 3.1 三种链表对比

| 类型   | 结构特点                                    | 典型场景             |
| ---- | --------------------------------------- | ---------------- |
| 单链表  | 每个节点含 data + next 指针，尾节点 next 为 NULL    | 最节省空间，基础题型       |
| 双链表  | 节点含 prev + data + next，可双向遍历            | 需要频繁前后移动（如文本编辑器） |
| 循环链表 | 尾节点 next 指向头节点（环形），遍历终止条件是"回到起点"而非 NULL | 约瑟夫环、轮询调度        |

### 3.2 节点定义与基本操作（单链表）

```cpp
struct Node {
    int data;
    Node* next;
    Node(int x) : data(x), next(nullptr) {}
};

// 头插法建表（结果与输入顺序相反）
Node* head = nullptr;
void insertHead(int x) {
    Node* p = new Node(x);
    p->next = head; head = p;
}

// 在节点 cur 之后插入 x —— O(1)
void insertAfter(Node* cur, int x) {
    Node* p = new Node(x);
    p->next = cur->next;
    cur->next = p;
}

// 删除 cur 之后的节点 —— O(1)
void deleteAfter(Node* cur) {
    Node* p = cur->next;
    cur->next = p->next;
    delete p;
}

// 遍历
for (Node* p = head; p != nullptr; p = p->next)
    cout << p->data << " ";
```

```
插入/删除的**指针顺序**：必须先接新节点的 next，再改前驱的 next。若先 `cur->next = p` 会丢失后半段链表——判断题经典陷阱。
```

### 3.3 链表反转（迭代法，必背）

```cpp
Node* reverse(Node* head) {
    Node* prev = nullptr;
    Node* cur = head;
    while (cur != nullptr) {
        Node* nxt = cur->next;  // 1. 先保存后继
        cur->next = prev;       // 2. 反转当前指针
        prev = cur;             // 3. prev 前移
        cur  = nxt;             // 4. cur 前移
    }
    return prev;             // prev 是新头节点
}
```

### 3.4 链表 vs 数组（选择题必考）

| 操作           | 数组         | 链表             |
| ------------ | ---------- | -------------- |
| 随机访问第 i 个元素  | O(1) ✓ 快   | O(n) 需从头遍历     |
| 在已知位置插入 / 删除 | O(n) 需整体移动 | O(1) ✓ 只改指针    |
| 存储方式         | 连续内存       | 分散内存（节点动态分配）   |
| 额外空间         | 无          | 每个节点多一个（或两个）指针 |

```
"链表支持随机访问" —— **错**；"链表插入删除一定比数组快" —— ** 错**（若先要 O(n) 查找位置，总代价仍是 O(n)）。注意题干是否已"知道位置"。

OJ 上也可用"数组模拟链表"（next 数组）实现，避免 new/delete 的开销和内存泄漏问题，考场上更稳妥。

练习：约瑟夫问题（n 人围圈报数出圈，循环链表经典题）；合并两个有序链表；找到链表中间节点（快慢指针）。

  4
```

## 二分查找与二分答案

```
  重点

  考核目标：掌握二分查找在有序数组中定位目标值；掌握二分答案（二分枚举法）解决"最大最小值 / 最小最大值"类问题。
```

### 4.1 二分查找（标准模板）

```cpp
int binarySearch(int a[], int n, int target) {
    int left = 0, right = n - 1;        // 闭区间 [left, right]
    while (left <= right) {
        int mid = left + (right - left) / 2; // 防溢出写法
        if (a[mid] == target) return mid;
        else if (a[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;                            // 未找到
}
```

* 复杂度 O(log n) —— 每次“砍一半”，n 个元素最多比较 ⌈log₂n⌉+1 次。
* 前提：数组必须有序（判断题必考）。

  ① `mid = left + (right-left)/2` 防止 left+right 溢出；② 循环条件 `left <= right` 与 `mid±1` 必须配套，否则死循环或漏解——这是二分最常见的错误。

### 4.2 二分答案（二分枚举法）

```
当**直接求最优解很难，但“验证一个候选答案是否可行”很容易** 时使用。套路：
```

1. 确定答案范围 \[lo, hi]（如 1 \~ 数组最大值）
2. 二分取 mid 作为“候选答案”
3. 写一个 check(mid) 函数验证可行性
4. 可行则收紧上界（求最小）或下界（求最大），循环到收敛

```cpp
// 例：把数组分成 ≤m 段，求各段和的最大值最小是多少
bool check(int mid) {              // 每段和不超过 mid，最少需要几段
    int cnt = 1, sum = 0;
    for (int i = 0; i < n; i++) {
        if (sum + a[i] > mid) { cnt++; sum = a[i]; }
        else sum += a[i];
    }
    return cnt <= m;               // 段数不超限 → mid 可行
}

int lo = maxElement, hi = totalSum;   // 答案下界、上界
while (lo < hi) {                      // 求最小可行值
    int mid = lo + (hi - lo) / 2;
    if (check(mid)) hi = mid;         // 可行 → 尝试更小
    else lo = mid + 1;                // 不可行 → 必须更大
}
// lo 即答案
```

```
识别信号：题面出现“**最大值最小 / 最小值最大 / 至少 / 最多**”字样，优先想到二分答案。典型题：砍木材、跳石头、分巧克力。

练习：有序数组中查找第一个 ≥ x 的位置（lower_bound 思想）；"砍树"问题（H 米高度砍下的木材总和恰好 ≥ 需求，求最大 H）。

  5
```

## 递归算法

```
  重点

  考核目标：掌握递归的基本原理（递归出口 + 递归步骤），能分析递归的时空复杂度，了解递归优化策略与栈溢出风险。
```

### 5.1 递归三要素

1. 递归出口（base case）：终止条件，防止无限递归
2. 递归步骤：问题规模缩小，调用自身
3. 结果合并：由子问题答案构造原问题答案

```cpp
// 阶乘：O(n) 时间、O(n) 栈空间
long long fact(int n) {
    if (n <= 1) return 1;          // 出口
    return n * fact(n - 1);         // 步骤
}

// 汉诺塔：2^n - 1 次移动，O(2^n) —— 指数复杂度的典型例子
void hanoi(int n, char from, char via, char to) {
    if (n == 0) return;
    hanoi(n - 1, from, to, via);
    cout << from << " -> " << to << "\n";
    hanoi(n - 1, via, from, to);
}
```

### 5.2 复杂度分析（选择题高频）

| 递归例子               | 时间复杂度    | 说明             |
| ------------------ | -------- | -------------- |
| 阶乘 fact(n)         | O(n)     | 递归 n 层，每层 O(1) |
| 斐波那契 fib(n)（朴素双递归） | O(2ⁿ)    | 子问题大量重复计算      |
| 汉诺塔 hanoi(n)       | O(2ⁿ)    | 移动次数恰为 2ⁿ−1    |
| 二分查找递归版            | O(log n) | 每层规模减半         |

### 5.3 优化策略

* 记忆化搜索：把已算出的子问题答案存进数组，斐波那契从 O(2ⁿ) 优化到 O(n)
* 改递推（迭代）：自底向上递推，消除函数调用开销与栈空间
* 尾递归：递归调用是函数最后一步（可被编译器优化成循环）

  递归深度过大 → **栈溢出（stack overflow）**，是判断题高频考点。递归的空间复杂度至少等于递归深度 × 每层栈帧大小。

  做“输出递归函数结果”的选择题时，画**递归调用树** 逐层展开，别心算——尤其注意输出语句在递归调用之前（正序）还是之后（逆序）。

  练习：用递归求 n 的各位数字之和；递归实现字符串逆序输出；爬楼梯问题（一次 1 或 2 阶，多少种走法 = 斐波那契）。

  ```
  6
  ```

## 分治算法：归并排序与快速排序

```
  重点

  考核目标：理解"分解—解决—合并"的分治思想，能实现归并排序和快速排序，并分析其复杂度。
```

### 6.1 分治三步曲

```
**分解**（Divide：拆成子问题）→ ** 解决**（Conquer：递归求解子问题）→ ** 合并**（Combine：把子问题答案合并）。子问题相互独立、与原问题结构相同。
```

### 6.2 归并排序

```cpp
int tmp[N];
void mergeSort(int a[], int l, int r) {
    if (l >= r) return;
    int mid = (l + r) / 2;
    mergeSort(a, l, mid);          // 分：左半
    mergeSort(a, mid + 1, r);    // 分：右半
    int i = l, j = mid + 1, k = l;
    while (i <= mid && j <= r)      // 治：合并两个有序段
        tmp[k++] = (a[i] <= a[j]) ? a[i++] : a[j++];
    while (i <= mid)  tmp[k++] = a[i++];
    while (j <= r)    tmp[k++] = a[j++];
    for (i = l; i <= r; i++) a[i] = tmp[i];
}
```

* 任何情况都是 O(n log n)，稳定排序（相等元素相对顺序不变——判断题常考）
* 需要 O(n) 辅助数组（空间换时间）
* 额外应用：逆序对计数（合并时左段元素 > 右段元素，则产生 mid−i+1 个逆序对）

### 6.3 快速排序

```cpp
void quickSort(int a[], int l, int r) {
    if (l >= r) return;
    int i = l, j = r, pivot = a[(l + r) / 2];
    while (i <= j) {
        while (a[i] < pivot) i++;
        while (a[j] > pivot) j--;
        if (i <= j) { swap(a[i], a[j]); i++; j--; }
    }
    quickSort(a, l, j);
    quickSort(a, i, r);
}
```

|      | 归并排序         | 快速排序                  |
| ---- | ------------ | --------------------- |
| 平均时间 | O(n log n)   | O(n log n)            |
| 最坏时间 | O(n log n) ✓ | O(n²)（数组已有序 + 选端点为基准） |
| 额外空间 | O(n)         | O(log n)（递归栈平均）       |
| 稳定性  | 稳定           | 不稳定                   |
| 分治重心 | 合并难、分解易（对半分） | 分解难（按基准分区）、合并易        |

```
“快排是稳定排序”——**错**；“快排在所有情况下都优于归并”——** 错**（最坏 O(n²) 且已序数组是典型反例）。快排基准选中间元素可缓解最坏情况。

练习：手写归并排序并统计逆序对；用快排思想求“第 k 小的数”（快速选择，平均 O(n)）。

  7
```

## 贪心算法

```
  重点

  考核目标：理解贪心的“局部最优 → 全局最优”思想与最优子结构，能用贪心解决经典问题，并知道贪心何时失效。
```

### 7.1 核心思想

* 每一步都选择当前看起来最优的选项，且不可回退（与回溯/搜索的本质区别）
* 最优子结构：问题的最优解包含子问题的最优解（贪心可行的必要条件）
* 贪心选择性质：局部最优选择能推出全局最优解——需要证明或凭经验判断

### 7.2 经典题型与策略

| 问题                      | 贪心策略             |
| ----------------------- | ---------------- |
| 活动选择 / 区间调度（最多不重叠区间）    | 按右端点从小到大排序，能选就选  |
| 区间覆盖（最少区间覆盖目标段）         | 按左端点排序，每次选能延伸最远的 |
| 部分背包（物品可分割）             | 按性价比（价值/重量）降序装   |
| 找零（面额体系如 1/5/10/50/100） | 优先使用大面额          |
| 排队打水（总等待时间最短）           | 打水时间短的人先打        |

```cpp
// 活动选择：n 个活动 [s, e)，求最多参加几个（互不重叠）
sort(v.begin(), v.end(), [](const PII& a, const PII& b) {
    return a.second < b.second;          // 按结束时间排序
});
int cnt = 0, lastEnd = -1e9;
for (auto& [s, e] : v)
    if (s >= lastEnd) { cnt++; lastEnd = e; }  // 不冲突就选
```

```
贪心**不一定** 得到全局最优！经典反例：① 0-1 背包（不可分割）贪心失效，需动态规划；② 找零问题若面额为 1、3、4，凑 6 元时贪心（4+1+1=3 枚）劣于最优（3+3=2 枚）。判断题常考“贪心总能得到最优解”——**错**。

排序是贪心的“另一半”——90% 的贪心题第一步是设计正确的排序关键字。写代码前先用小样例手动验证策略。

练习：NOIP 经典"导弹拦截"的贪心变形、"线段覆盖"问题、"均分纸牌"问题。

  8
```

## 算法复杂度估算

```
  客观题必考

  考核目标：掌握多项式复杂度、指数复杂度、对数复杂度的估算方法，能根据数据规模判断算法是否会超时。
```

### 8.1 常见复杂度从快到慢

| 复杂度        | 名称   | 典型算法              | n=10⁶ 时可否通过   |
| ---------- | ---- | ----------------- | ------------- |
| O(1)       | 常数   | 数组随机访问、数学公式       | ✓ 轻松          |
| O(log n)   | 对数   | 二分查找、快速幂          | ✓ 轻松（仅约 20 步） |
| O(n)       | 线性   | 一次遍历、埃氏筛均摊        | ✓ 可以          |
| O(n log n) | 线性对数 | 归并/快排、sort        | ✓ 勉强          |
| O(n²)      | 平方   | 冒泡/选择/插入排序、双重循环   | ✗ n>10⁴ 就危险   |
| O(n³)      | 立方   | 三重循环、Floyd        | ✗ 仅 n≤500     |
| O(2ⁿ)      | 指数   | 朴素斐波那契递归、汉诺塔、子集枚举 | ✗ n>30 就跑不动   |
| O(n!)      | 阶乘   | 全排列枚举             | ✗ n≤10        |

### 8.2 估算技巧

* 看循环嵌套层数：单层 O(n)、双层 O(n²)；但“每次规模减半”的循环是 O(log n)（如 while(n) n/=2）
* 递归：复杂度 ≈ 递归树节点总数 × 每节点代价；T(n)=2T(n/2)+O(n) → O(n log n)（归并）；T(n)=T(n/2)+O(1) → O(log n)（二分）
* 经验法则：1 秒大约执行 10⁸\~10⁹ 次基本操作。看到 n≤10⁶ 就要求 O(n)\~O(n log n)；n≤3000 允许 O(n²)
* 空间复杂度：数组大小 + 递归栈深度。链表每个节点的指针也是空间

  渐进符号区别：`O`（上界）、`Ω`（下界）、`Θ`（紧确界）。"快排是 O(n log n) 的算法"严格说指平均情况，最坏为 O(n²)——选择题措辞陷阱。

  练习：分析双重循环 for(i=1;i1 的残余因子）
* 高精度加、减（借位+去前导零）、乘（高×低）、除（从高位除）
* 链表反转四步法
* 二分查找与二分答案两套模板（边界写法）
* 归并排序、快速排序

### 9.3 常见失分点汇总

1. 1 不是素数、2 是偶素数
2. lcm 先乘后除导致溢出
3. 高精度减法不判大小、不去前导零
4. 链表插入先改前驱指针导致断链
5. 二分边界写成死循环
6. 循环链表遍历条件写成 != nullptr（应为回到起点）
7. 快排被误认为稳定排序
8. 忽略 long long / 数组开小

### 9.4 真题与刷题资源

* 官方真题下载：CCF GESP 官网 gesp.ccf.org.cn（历年认证真题均可免费获取）
* 在线刷题：洛谷 GESP 题单（含历年五级真题，可在线评测）
* 按星级刷题：上海市计算机学会竞赛平台 iai.sh.cn（三星\~四星难度对应五级）
* 考前建议：完整限时模拟 2\~3 套近年真题（180 分钟掐表）

  近年真题编程题趋势：① 数论类（质因数分解 + 条件筛选数字）；② 高精度/分数与小数互化类。两者都在本资料第 1、2 章覆盖范围内。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.1l.lol/gesp-zone/gesp/gesp-5.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
