电子学会🐍 Python🔧 C++GESP🐍 Python🔧 C++
__CRUMBS__

CCF GESP 编程能力等级认证(C++)· 知识点手册

依据《CCF 编程能力等级认证 C++&Python 认证标准》整理,C++ 考题以 C++11 标准为准。GESP 由中国计算机学会主办,C++ 编程认证共一至八级,从 C++ 入门到图论与算法优化逐级递进。每个知识点均配有详细讲解、C++ 代码示例、易错点提示,并配模拟题讲解;难懂处附适合小学生、初中生的生活化例子。

共 8 个等级题型:15 单选 + 10 判断 + 2 编程1-4 级 120 分钟 · 5-8 级 180 分钟附各等级模拟题

考纲总览

等级递进 · 能力要求 · 题型说明

一、等级逻辑关系总览

《CCF 编程能力等级认证 C++&Python 认证标准》由中国计算机学会(CCF)制定,认证划分为一至八级,' '为学习者提供编程能力水平的证明。考试采用上机考试,题型为单选题(15 道 × 2 分)+ 判断题(10 道 × 2 分)+ 编程题(2 道 × 25 分),' '1-4 级考试时间 120 分钟,5-8 级 180 分钟

八级内容呈现明显的螺旋递进:基础语法 → 函数与算法 → 数据结构与搜索 → 动态规划与图论 → 综合与优化。

1
一级 · 编程入门
基础语法与图形编程
2
二级 · 基础拓展
存储网络知识与函数
3
三级 · 数据与算法起步
编码、数组、字符串、枚举模拟
4
四级 · 函数与算法进阶
函数、排序、递推、文件异常
5
五级 · 数论与高效算法
数论、二分、递归、分治贪心
6
六级 · 数据结构与搜索
树、搜索、背包、类、栈队列
7
七级 · 动态规划与图论
复杂DP、图、哈希
8
八级 · 综合与优化
组合、图论综合、优化

二、各级别核心能力要求总览

[['1', '一级 · 编程入门', '基础语法与图形编程', '了解计算机构成与开发环境,能独立完成简单功能的顺序、分支、循环程序'], ['2', '二级 · 基础拓展', '存储网络知识与函数', '掌握类型转换、数学库函数,能独立完成多分支与循环嵌套程序'], ['3', '三级 · 数据与算法起步', '编码、数组、字符串、枚举模拟', '掌握编码、进制、位运算,能用枚举法、模拟法解决实际问题'], ['4', '四级 · 函数与算法进阶', '函数、排序、递推、文件异常', '掌握模块化编程、递推、排序算法,理解复杂度,会文件读写'], ['5', '五级 · 数论与高效算法', '数论、二分、递归、分治贪心', '掌握数论与二分、递归、分治、贪心算法,能选择合适算法'], ['6', '六级 · 数据结构与搜索', '树、搜索、背包、类、栈队列', '掌握树与搜索算法、简单动态规划、面向对象,会使用栈队列'], ['7', '七级 · 动态规划与图论', '复杂DP、图、哈希', '掌握复杂动态规划、图论基础算法与哈希表'], ['8', '八级 · 综合与优化', '组合、图论综合、优化', '掌握组合数学、图论综合应用与算法优化']]
说明:GESP 考纲按"知识点详述 + 知识块 + 知识点描述"给出,未提供官方样题;本手册按知识点整理卡片,并以贴合考点的模拟题代替真题进行讲解。

三、使用说明与备考建议总览

  • 本手册按 GESP 8 级顺序将知识点拆分为卡片,左侧导航可快速跳转;
  • 每个知识点包含概念讲解 → 代码示例 → 易错点 → 模拟题的完整闭环,难懂处配“生活小例子”;
  • 代码块右上角有“复制”按钮,例题答案可点击“查看答案”展开;
  • 顶部可一键切换 电子学会 Python / 电子学会 C++ / GESP Python / GESP C++ 四本手册。
1

一级 · 编程入门

计算机基础 · 编程规范 · 变量与数据类型 · 顺序/分支/循环 · 输入输出 · Turtle 绘图
能力目标:了解计算机构成与开发环境,能独立完成简单功能的顺序、分支、循环程序核心:基础语法与图形编程

1.1 计算机基础与编程环境第一级知识

① 计算机的组成与历史

  • 硬件:CPU(大脑)、内存、I/O 设备(键盘鼠标/显示器);
  • 软件:操作系统(Windows、Linux)、应用软件;
  • 历史:电子管 → 晶体管 → 集成电路 → 大规模集成电路。

② 集成开发环境(Dev C++ 等)

GESP 一级要求会用 IDE:创建文件、编辑、保存、编译、运行、调试。C++ 源文件后缀是 .cpp

先编译(把代码变成机器语言)再运行。编译错误 error 和警告 warning 要区分:error 必须改。

1.2 程序框架与基本语句第一级知识

① 程序框架

C++
#include <iostream>
using namespace std;
int main() {
    // 程序从这里开始
    return 0;
}

② cin / cout / 赋值

C++
int a, b;
cin >> a >> b;          // 输入
cout << a + b << endl;  // 输出 + 换行
a = 10;                 // 赋值语句
a++;                    // 自增 a = a + 1
a--;                    // 自减 a = a - 1
生活小例子:厨房的锅碗瓢盆

#include <iostream> 就像把厨具柜搬进来(引入工具箱),main()厨房操作台(程序起点),cin>>=把菜拿进厨房、cout<<=把菜端上桌。每句结尾都要加分号,就像说话要加句号!

易错点:① main 是程序入口;② 每句以分号结尾;③ >> 是输入方向、<< 是输出方向。

1.3 标识符、常量与变量第一级知识

① 标识符与关键字

  • 标识符:变量/函数名,由字母、数字、下划线组成,不能以数字开头,区分大小写;
  • 关键字:int、if、for、while 等,不能作变量名

② 常量与变量

C++
const int MAXN = 100;   // 常量(不可改)
int age = 10;            // 变量:声明 + 初始化
age = 11;                // 修改变量的值
int n;                   // 先声明(不初始化)
生活小例子:贴标签的储物箱

变量像贴了名字的储物箱int age 先造一个能装整数的箱子并起名 age,= 10 往里放 10。常量 const 像上了锁的保险箱,放进去就不能改。

易错点:① 变量先声明后使用;② 变量名不能以数字开头、不能用关键字;③ 未初始化直接用的值是不确定的

1.4 基本数据类型第一级知识

① 四种基本类型

类型占位含义示例
int
%d
int a = 10;

② printf / scanf 基础

C++
int a = 10;
double b = 3.14;
char c = 'A';
printf("%d %.2lf %c\n", a, b, c);
scanf("%d", &a);     // 注意 & 取地址
易错点:① int 不够大用 long long;② 小数默认是 double;③ char 用单引号,字符串用双引号;④ scanf 要加 &

1.5 基本运算第一级知识

① 算术运算

C++
int a = 7, b = 2;
cout << a + b << " " << a - b << " ";   // 9 5
cout << a * b << " " << a / b << " ";   // 14 3(整数除法)
cout << a % b << endl;                  // 1(取余)

② 关系运算与逻辑运算

类别符号例子结果
> < >= <= == !=
3 > 2
true
易错点:① 整数除法直接舍去小数(7/2=3);② % 取余;③ 判断相等用 ==,赋值用 =,别混!

1.6 顺序、分支与循环第一级知识

① if / if-else / switch

C++
int s = 85;
if (s >= 90) cout << "A";
else if (s >= 80) cout << "B";
else cout << "C";
// switch
int d = 2;
switch (d) {
    case 1: cout << "一"; break;
    case 2: cout << "二"; break;
    default: cout << "其他";
}

② for / while / do-while

C++
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;   // 5050
int j = 0;
while (j < 5) { cout << j; j++; }         // 01234
int k = 0;
do { cout << k; k++; } while (k < 3);     // 012 至少执行一次
易错点:① 循环里用 break 跳出、continue 跳过本次;② do-while 至少执行一次;③ switch 的 case 记得 break

一级模拟题第一级知识

以下例题贴合 GESP 一级考点(环境、语句、数据类型、运算、结构)设计:

选择题以下哪个是合法的 C++ 变量名?
A. 2nameB. intC. my_nameD. my-name
答案:C
不能以数字开头(A)、不能用关键字(B)、不能含减号(D)。
选择题7 / 2 的结果是?
A. 3.5B. 3C. 2D. 报错
答案:B
整数除法直接舍去小数部分,7/2=3。
选择题以下能正确输入一个整数到变量 a 的是?
A. cin >> aB. cout >> aC. cin << aD. scanf("%d", a)
答案:A
cin 用 >>;scanf 必须写 &a
编程题输入两个整数,输出较大的那个。
答案:参考程序见解析
int a, b; cin >> a >> b;
if (a > b) cout << a;
else cout << b;
用 if 分支比较即可。
编程题用循环求 1+2+…+n。
答案:参考程序见解析
int n, sum = 0; cin >> n;
for (int i = 1; i <= n; i++) sum += i;
cout << sum;
累加循环,别忘了 sum 初始为 0。
2

二级 · 基础拓展

存储与网络 · 程序设计语言 · 流程图 · ASCII · 类型转换 · 多层分支/循环 · 数学函数
能力目标:掌握类型转换、数学库函数,能独立完成多分支与循环嵌套程序核心:存储网络知识与函数

2.1 计算机存储与网络第二级知识

① 存储器:RAM / ROM / Cache

存储器特点用途
RAM

② 计算机网络

  • 分类:局域网 LAN(教室)、城域网 MAN(城市)、广域网 WAN(互联网);
  • TCP/IP 四层:应用层、传输层、网络层、网络接口层;OSI 七层:物理层…应用层;
  • IP 地址:设备在网络的"门牌号"(如 192.168.1.1)。
易错点:① RAM 断电丢、ROM 断电不丢;② 互联网是最大的广域网;③ IP 地址用于寻址。

2.2 程序设计语言与流程图第二级知识

① 语言分类

语言特点例子
0101 1010

② 流程图

圆角矩形=开始/结束,平行四边形=输入输出,矩形=处理,菱形=判断,箭头=流程方向。

易错点:① C++ 是编译型高级语言;② 流程图判断用菱形;③ 三种基本结构都能用流程图表示。

2.3 ASCII 编码第二级知识

① 关键 ASCII 码

字符ASCII
32

② 字符与编码转换

C++
char c = 'A';
cout << (int)c << endl;      // 65(字符转编码)
cout << (char)97 << endl;    // 'a'(编码转字符)
// 小写 = 大写 + 32
cout << (char)('A' + 32) << endl;   // 得到字符 a 
易错点:① '0'=48、'A'=65、'a'=97、空格=32;② 大写转小写 +32;③ (int)c 取编码。

2.4 数据类型转换第二级知识

① 强制转换 vs 隐式转换

C++
int a = 5, b = 2;
double d = (double)a / b;     // 强制转换:2.5
cout << d << endl;
// 隐式转换:int 参与小树运算自动变 double
cout << 5 / 2.0 << endl;       // 2.5
cout << (char)65 << endl;      // 得到字符 A 
易错点:① 整数除法想得到小数,先把一边转成 double;② 隐式转换:低精度自动提升为高精度;③ 强制转换用 (类型)值

2.5 多层分支与多层循环第二级知识

① 分支嵌套

C++
int score = 85;
if (score >= 60) {
    if (score >= 90) cout << "优秀";
    else cout << "合格";
} else cout << "不合格";

② 循环嵌套

C++
for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++)
        cout << "*";
    cout << endl;
}
易错点:① 嵌套 if 注意else 与最近的 if 配对;② 内层循环随外层变化;③ 打印图形记得换行。

2.6 数学函数第二级知识

① 常用数学函数

C++
#include <cmath>
#include <cstdlib>
cout << abs(-5) << endl;          // 5  绝对值
cout << sqrt(25) << endl;         // 5  平方根
cout << max(3, 7) << " " << min(3, 7) << endl;  // 7 3
cout << pow(2, 10) << endl;       // 1024 乘方
// 随机数:先播种再取
srand(time(0));
int x = rand() % 6 + 1;           // 1~6 随机
cout << x;
易错点:① 平方根 sqrt#include <cmath>;② rand()%n 得到 0~n-1;③ pow 返回 double。

二级模拟题第二级知识

选择题断电后数据丢失的存储器是?
A. ROMB. RAMC. 硬盘D. 光盘
答案:B
RAM(内存)断电丢失。
选择题字符 "A" 的 ASCII 码是?
A. 32B. 48C. 65D. 97
答案:C
A=65、a=97、0=48、空格=32。
选择题5 / 2.0 的结果是?
A. 2B. 2.5C. 报错D. 2.0
答案:B
一边是 double,发生隐式转换,得到 2.5。
编程题用嵌套循环打印 1 到 9 的乘法表。
答案:参考程序见解析
for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++)
        cout << j << "*" << i << "=" << i*j << "\t";
    cout << endl;
}
外层乘数 i,内层从 1 到 i。
3

三级 · 数据与算法起步

数据编码 · 进制转换 · 位运算 · 算法描述 · 数组/容器 · 字符串 · 枚举法 · 模拟法
能力目标:掌握编码、进制、位运算,能用枚举法、模拟法解决实际问题核心:编码、数组、字符串、枚举模拟

3.1 数据编码:原码、反码、补码第三级知识

① 三种编码

编码正数负数说明
1 +
0 1

例:8 位表示 -3:原码 10000011,反码 11111100,补码 11111101

易错点:① 负数在计算机里以补码存储;② ~x = -x-1(按位取反);③ 正数三种编码相同。

3.2 进制转换第三级知识

① 常见进制

进制基数数字例子
2
0,1
1010=10

② 十进制转二进制(除 2 取余)

C++
int n = 13, a[100], cnt = 0;
while (n) { a[cnt++] = n % 2; n /= 2; }
for (int i = cnt - 1; i >= 0; i--) cout << a[i];   // 1101
// 流操作输出
cout << hex << 255 << endl;   // ff
生活小例子:4 盏灯的开关

二进制想成4 盏灯(亮=1 灭=0),分量 8、4、2、1:表示 13 就亮 8+4+1 三盏 → 1101。电脑里所有数据都是灯的亮灭!

易错点:① 除 2 取余要倒序输出;② 十六进制 A~F 表示 10~15;③ 位权:二进制第 k 位 = 2^k。

3.3 位运算第三级知识

① 位运算符

符号含义例子结果
&
5 & 3
1
C++
int n = 7;
cout << (n & 1) << endl;    // 1 → 奇数(判奇偶)
cout << (n << 1) << endl;   // 14(×2)
cout << (n >> 1) << endl;   // 3(÷2)
int a = 5, b = 3;
a ^= b; b ^= a; a ^= b;     // 交换 a、b
易错点:x & 1 判奇偶;② 左移=×2、右移=÷2;③ 位运算优先级低,加括号 (n & 1)

3.4 算法的概念与描述第三级知识

① 什么是算法

算法 = 解决某问题的明确步骤。三种描述方式:

  • 自然语言:日常话描述步骤;
  • 流程图:图形化(菱形判断、矩形处理);
  • 伪代码:像代码但不要求能运行。
输入数据处理(计算/判断)输出结果
易错点:① 算法要有穷、明确、可行;② 三种描述表达同一算法;③ 同一问题可有多个算法(效率不同)。

3.5 一维数组第三级知识

① 数组的定义与使用

C++
int a[100];          // 定义能存 100 个整数的数组
int n; cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
// 下标从 0 开始
int sum = 0, mx = a[0];
for (int i = 0; i < n; i++) {
    sum += a[i];
    if (a[i] > mx) mx = a[i];
}
cout << sum << " " << mx << endl;
生活小例子:一排编号储物柜

数组想成一排编号从 0 开始的储物柜:a[0] 是 0 号柜、a[1] 是 1 号柜……循环下标 i 就是挨个开柜子。柜子容量开多大很重要,开小了会"爆柜"(越界)。

易错点:① 数组下标从 0 开始;② 定义时大小要足够大,防止越界;③ 数组整体不能直接赋值/比较。

3.6 字符串及其函数第三级知识

① 字符串常用函数

C++
#include <string>
string s = "Hello World";
cout << s.size() << endl;         // 11 长度
cout << s.substr(0, 5) << endl;   // "Hello" 截取
cout << s.find("World") << endl;  // 6 查找位置
s += "!";                         // 拼接
// 大小写转换
for (char &c : s) if (c >= 'A' && c <= 'Z') c += 32;
cout << s << endl;                // 全小写
函数作用
s.size()
易错点:① 下标从 0 开始;② find 找不到返回 string::npos;③ 单个字符是 char,用单引号。

3.7 枚举法与模拟法第三级知识

① 枚举法

C++
// 水仙花数:各位立方和等于本身
for (int n = 100; n <= 999; n++) {
    int a = n / 100, b = n / 10 % 10, c = n % 10;
    if (a*a*a + b*b*b + c*c*c == n)
        cout << n << " ";     // 153 370 371 407
}

② 模拟法

C++
// 模拟:猴子吃桃,第 10 天剩 1 个,每天吃一半多一个
int x = 1;
for (int d = 9; d >= 1; d--) x = (x + 1) * 2;
cout << x << endl;    // 1534
生活小例子:翻抽屉找钥匙

枚举=钥匙丢了挨个抽屉翻,不重不漏;模拟=照着说明书一步步做,题目怎么说代码就怎么写。一个是"穷举验证",一个是"原样还原过程"。

易错点:① 枚举要范围完整、条件正确;② 模拟关键是读懂题意按过程实现;③ 数据大时枚举太慢。

三级模拟题第三级知识

选择题-5 的 8 位补码是?
A. 10000101B. 11111011C. 11111010D. 00000101
答案:B
原码 10000101 → 反码 11111010 → 补码 = 反码+1 = 11111011。
选择题十进制 13 的二进制是?
A. 1010B. 1101C. 1110D. 1001
答案:B
13 = 8+4+1 → 1101。
选择题8 >> 2 的值是?
A. 16B. 4C. 2D. 1
答案:C
右移一位÷2,右移两位÷4:8÷4=2。
编程题输入 n 个数,输出其中的最大值。
答案:参考程序见解析
int n; cin >> n;
int a[100]; for (int i=0;i<n;i++) cin >> a[i];
int mx = a[0];
for (int i=1;i<n;i++) if (a[i]>mx) mx=a[i];
cout << mx;
遍历数组找最大。
编程题用枚举法统计 1~100 中能被 3 整除又能被 5 整除的数。
答案:参考程序见解析
for (int i=1;i<=100;i++)
    if (i%3==0 && i%5==0) cout << i << " ";
i%3==0 && i%5==0 即能被 15 整除。
4

四级 · 函数与算法进阶

函数 · 作用域 · 参数传递 · 复合类型嵌套 · 递推 · 排序 · 复杂度 · 文件 · 异常
能力目标:掌握模块化编程、递推、排序算法,理解复杂度,会文件读写核心:函数、排序、递推、文件异常

4.1 指针类型第四级知识

① 指针的概念

指针存的是变量的地址(门牌号)。定义、赋值、解引用:

C++
int x = 10;
int *p = &x;       // p 存 x 的地址(&取地址)
cout << *p << endl;  // 10(*p 解引用取值)
*p = 20;           // 通过指针改 x
cout << x << endl; // 20
生活小例子:门牌号

指针想成写着门牌号的纸条:变量 x 在地址 &x 的房子,p 拿的是纸条*p 就是顺着纸条找到房子,开门看里面的值。指针能让你"绕过去"修改别的变量!

易错点:&x 取地址、*p 解引用;② 指针必须先赋值再用,否则是野指针;③ 指针类型要和指向的变量一致。

4.2 函数:定义、调用、形参实参第四级知识

① 函数的定义与调用

C++
int add(int a, int b) {   // a、b 形参
    return a + b;
}
int main() {
    int r = add(3, 5);        // 3、5 实参
    cout << r << endl;        // 8
    return 0;
}

② 函数声明 / 定义 / 调用

  • 声明:告诉编译器函数长什么样(可放 main 前);
  • 定义:写函数体;
  • 形参=菜谱里的"适量",实参=实际放的量。
易错点:① 有返回值要 return;② return 后语句不执行;③ 函数一般写在 main 之前或先声明。

4.3 作用域与参数传递第四级知识

① 全局与局部

C++
int g = 100;              // 全局变量(整个文件可见)
void f() { cout << g; }    // 函数里能用全局
int main() {
    int g = 5;             // 局部变量(遮蔽全局)
    cout << g << endl;     // 5
    return 0;
}

② 值传递 / 引用传递 / 指针传递

C++
void by_val(int x) { x = 99; }        // 改不了外面
void by_ref(int &x) { x = 99; }        // 能改
void by_ptr(int *x) { *x = 99; }       // 也能改
int a = 1;
by_val(a); cout << a << endl;   // 1
by_ref(a); cout << a << endl;   // 99
by_ptr(&a); cout << a << endl;  // 99
生活小例子:复印件与原件

值传递=给对方复印件,改复印件原件没事;引用/指针=给对方原件地址,对方一改原件就变。想通过函数改外面的变量,就用引用 & 或指针 *。

易错点:① 值传递复制一份,不改变外面;② 引用 & 和指针 * 能修改实参;③ 数组传参会"退化"成指针。

4.4 结构体第四级知识

① 结构体的定义与使用

C++
struct Student {
    string name;
    int score;
};
Student a[100];                 // 结构体数组
cin >> a[0].name >> a[0].score;
cout << a[0].name << " " << a[0].score;
// 结构体排序
bool cmp(Student x, Student y) {
    return x.score > y.score;   // 按分数从高到低
}
sort(a, a + n, cmp);
生活小例子:学生档案袋

结构体想成一个学生档案袋:里面装着名字、分数、班级(不同数据合在一起)。struct 就是先规定档案袋里装什么,然后每个学生一个袋子。

易错点:① 用 . 访问成员;② 结构体可以放进数组、做函数参数;③ 排序时自定义 cmp 规则。

4.5 二维数组与多维数组第四级知识

① 二维数组

C++
int a[10][10];           // 行 × 列
for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        cin >> a[i][j];
// 求和
int sum = 0;
for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        sum += a[i][j];
cout << sum;
生活小例子:电影院座位表

二维数组想成电影院座位表:a[i][j] 就是第 i 排第 j 座,先定排(i)再定座(j)。双层循环 = 一排排挨个查座位

易错点:① 写法 a[行][列];② 双层循环先外层行、内层列;③ 行和列别搞反。

4.6 递推算法第四级知识

① 递推

C++
// 斐波那契
long long f[100];
f[1] = f[2] = 1;                       // 初始值
for (int i = 3; i <= n; i++)
    f[i] = f[i - 1] + f[i - 2];        // 递推关系
cout << f[n] << endl;
生活小例子:爬楼梯

到第 n 级只能从 n-1 走 1 步或 n-2 走 2 步 → f(n)=f(n-1)+f(n-2)先算小的,再推大的,就是递推。比递归快,因为不重复算。

易错点:① 先定初始值再写关系式;② 用 long long 防溢出;③ 递推=循环实现,递归=自己调自己。

4.7 排序算法与稳定性第四级知识

① 冒泡 / 插入 / 选择

C++
// 冒泡:相邻比较,大的往后
void bubble(int a[], int n) {
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - 1 - i; j++)
            if (a[j] > a[j+1]) swap(a[j], a[j+1]);
}
// 选择:每轮挑最小放前面(不稳定)
void select(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int mn = i;
        for (int j = i + 1; j < n; j++)
            if (a[j] < a[mn]) mn = j;
        swap(a[i], a[mn]);
    }
}

② 稳定性与复杂度

稳定性:相等元素排序后相对顺序不变。冒泡、插入稳定;选择不稳定。三种都是 O(n²)。

生活小例子:三种排队法

冒泡=相邻比个子高往后挪;选择=每轮挑最矮的站前面;插入=打扑克抓牌插入合适位置。排序稳定性就像同分数的学生按原来先后顺序排,不乱插队。

易错点:① 三种排序 O(n²);② 冒泡/插入稳定、选择不稳定;③ 库函数 sort 更快(O(n log n))。

4.8 算法复杂度估算第四级知识

① 时间复杂度

结构复杂度例子
O(1)
a = 1
易错点:① 一层循环 O(n)、两层 O(n²);② 只保留最高次项;③ n=10⁵ 时 O(n²)=10¹⁰ 会超时,要考虑优化。

4.9 文件读写与异常处理第四级知识

① 文件重定向

C++
freopen("in.txt", "r", stdin);     // 从文件读
freopen("out.txt", "w", stdout);   // 写到文件
int a; cin >> a; cout << a;        // 正常写代码
fclose(stdin); fclose(stdout);

② 文件读写流

C++
#include <fstream>
ifstream fin("in.txt");
ofstream fout("out.txt");
int x; fin >> x;         // 读
fout << x * 2;           // 写
fin.close(); fout.close();
易错点:freopen 重定向后 cin/cout 自动转向文件;② 读不到数据时 fin 状态变 false;③ GESP 机试常用 freopen 读文件。

四级模拟题第四级知识

选择题下列哪种方式能在函数内修改 main 中的变量 x?
A. 值传递B. 引用传递C. 复制传递D. 常量传递
答案:B
引用(或指针)传递能修改实参;值传递不行。
选择题选择排序的稳定性是?
A. 稳定B. 不稳定C. 视情况D. 无定义
答案:B
选择排序可能交换相等元素,不稳定。
选择题int *p = &x; 中 *p 表示?
A. x 的地址B. x 的值C. p 的地址D. 报错
答案:B
解引用 *p 得到指针指向变量的值。
编程题用结构体存学生姓名和分数,按分数降序排序。
答案:参考思路见解析
struct S{ string name; int score; } a[100];
bool cmp(S x, S y){ return x.score > y.score; }
sort(a, a+n, cmp);
自定义 cmp 按分数降序。
编程题使用 freopen 从文件读入 n 个数并输出和。
答案:参考程序见解析
freopen("in.txt","r",stdin);
int n, sum=0, x; cin >> n;
for (int i=0;i<n;i++){ cin >> x; sum+=x; }
cout << sum;
重定向后 cin 从文件读取。
5

五级 · 数论与高效算法

初等数论 · 高精度 · 链表 · 筛法 · 二分 · 递归 · 分治 · 贪心
能力目标:掌握数论与二分、递归、分治、贪心算法,能选择合适算法核心:数论、二分、递归、分治贪心

5.1 初等数论第五级知识

① 素数判断

C++
bool is_prime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}

② 最大公约数与最小公倍数

C++
int gcd(int a, int b) {       // 辗转相除法
    while (b) { int t = a % b; a = b; b = t; }
    return a;
}
int lcm(int a, int b) { return a / gcd(a, b) * b; }

③ 素数与质因数分解

C++
// 质因数分解 12 = 2*2*3
int n = 12;
for (int i = 2; i * i <= n; i++)
    while (n % i == 0) { cout << i << " "; n /= i; }
if (n > 1) cout << n;      // 最后剩下的质数
// 输出 2 2 3
易错点:① 判断素数试到 √n;② gcd 用辗转相除法;③ 质因数分解:不断除最小质因子。

5.2 高精度运算(数组模拟)第五级知识

① 高精度加法

C++
// 数字倒着存数组:a[0] 是个位
int a[500] = {0}, b[500] = {0}, c[505] = {0};
string s1 = "12345678901234567890", s2 = "9876543210";
for (int i = 0; i < s1.size(); i++) a[i] = s1[s1.size()-1-i] - '0';
for (int i = 0; i < s2.size(); i++) b[i] = s2[s2.size()-1-i] - '0';
int len = max(s1.size(), s2.size());
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++;
for (int i = len - 1; i >= 0; i--) cout << c[i];
生活小例子:竖式加法

数字太大 long long 也装不下(超过 19 位)。高精度就是小学生列竖式:个位对齐、逐位相加、满十进一。数组倒着存(个位在 a[0])是为了进位方便!

易错点:① 数字倒序存数组,个位在下标 0;② 满十进一:c[i+1] += c[i]/10; c[i] %= 10;;③ 减法注意借位、乘法注意错位相加。

5.3 链表第五级知识

① 单链表的创建与操作

C++
struct Node { int data; Node *next; };
// 头插法建链表
Node *head = nullptr;
for (int i = 1; i <= 5; i++) {
    Node *p = new Node();
    p->data = i;
    p->next = head;    // 新结点指向原头
    head = p;
}
// 遍历
for (Node *p = head; p; p = p->next)
    cout << p->data << " ";

② 单 / 双 / 循环链表

  • 单链表:只能往后走(单向);
  • 双链表:有 prev 和 next,能双向走,删除更灵活;
  • 循环链表:尾结点指向头,形成环(约瑟夫问题常用)。
生活小例子:手拉手的小朋友

链表=小朋友手拉手排成一队,每个人只记着"下一个是谁"(next 指针)。插入就像中间塞进一个小朋友:先让他拉住前面的,再让前面的人改拉他。不用整队重排!

易错点:① 用 new 建结点、-> 访问成员;② 删除结点要先改指针再释放;③ 链表插入/删除 O(1),查找 O(n)。

5.4 埃氏筛与线性筛第五级知识

① 埃氏筛

C++
const int N = 1000000;
bool isp[N + 1];
void sieve(int n) {
    fill(isp, isp + n + 1, true);
    isp[0] = isp[1] = false;
    for (int i = 2; i * i <= n; i++)
        if (isp[i])
            for (int j = i * i; j <= n; j += i)   // 从 i*i 开始筛
                isp[j] = false;
}

② 线性筛(欧拉筛,O(n))

C++
vector<int> primes;
bool np[N + 1];
for (int i = 2; i <= n; i++) {
    if (!np[i]) primes.push_back(i);
    for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) {
        np[i * primes[j]] = true;
        if (i % primes[j] == 0) break;   // 保证每个数只被最小质因子筛一次
    }
}
易错点:① 埃氏筛从 i*i 开始,复杂度 O(n log log n);② 线性筛每个合数只筛一次 → O(n);③ 判断多个数是否为素数用筛法预处理。

5.5 二分查找与二分答案第五级知识

① 二分查找

C++
int a[100], n, x;
// 前提:a 已升序
int l = 0, r = n - 1, ans = -1;
while (l <= r) {
    int mid = (l + r) / 2;
    if (a[mid] == x) { ans = mid; break; }
    else if (a[mid] < x) l = mid + 1;
    else r = mid - 1;
}
cout << ans;

② 二分答案

C++
// 例:m 段绳子切成长度 len,每段最长多少
bool ok(int len, int a[], int m) {
    long long cnt = 0;
    for (int i = 0; i < n; i++) cnt += a[i] / len;
    return cnt >= m;
}
int l = 1, r = maxlen;
while (l <= r) {
    int mid = (l + r) / 2;
    if (ok(mid, a, m)) l = mid + 1;   // 还能更长
    else r = mid - 1;
}
cout << r;
生活小例子:翻字典猜页数

猜字在字典哪页:翻中间,大了往左翻、小了往右翻,每次砍一半——二分查找。二分答案=题目反过来问"最长能多长":猜一个长度,验证够不够,再收窄。100 万个数最多 20 次!

易错点:① 二分前提:有序或单调;② 区间收缩时注意 l=mid+1 / r=mid-1 防死循环;③ 验证函数 ok() 要写对。

5.6 递归算法第五级知识

① 递归

C++
int fact(int n) {
    if (n <= 1) return 1;      // 终止条件
    return n * fact(n - 1);    // 自己调自己
}
int fib(int n) {
    if (n <= 2) return 1;
    return fib(n - 1) + fib(n - 2);   // O(2^n) 太慢!
}

② 优化:记忆化

C++
long long memo[100];
long long fib2(int n) {
    if (n <= 2) return 1;
    if (memo[n]) return memo[n];      // 算过直接返回
    return memo[n] = fib2(n - 1) + fib2(n - 2);
}
生活小例子:套娃

递归像俄罗斯套娃:拆大娃得小娃,拆到最小的(终止条件),再一层层带回答案。记忆化像每拆一个娃都记下里面装了多少——下次遇到直接抄答案,不重复拆!

易错点:① 递归必须有终止条件;② 朴素斐波那契 O(2ⁿ),记忆化变 O(n);③ 递归过深会栈溢出,可改用递推。

5.7 分治算法:归并与快排第五级知识

① 归并排序

C++
void merge_sort(int a[], int l, int r) {
    if (l >= r) return;
    int mid = (l + r) / 2;
    merge_sort(a, l, mid);
    merge_sort(a, mid + 1, r);
    // 合并两个有序段
    int tmp[100], 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];
}

② 快速排序(选基准分两边)

C++
void quick_sort(int a[], int l, int r) {
    if (l >= r) return;
    int i = l, j = r, p = a[l];
    while (i < j) {
        while (i < j && a[j] >= p) j--;
        a[i] = a[j];
        while (i < j && a[i] <= p) i++;
        a[j] = a[i];
    }
    a[i] = p;
    quick_sort(a, l, i - 1);
    quick_sort(a, i + 1, r);
}
易错点:① 分治三步:分解→解决→合并;② 归并稳定 O(n log n),快排平均 O(n log n) 不稳定;③ 归并可顺带求逆序对。

5.8 贪心算法第五级知识

① 贪心

C++
// 活动安排:按结束时间排序,每次选最早结束且不冲突的
struct Act { int s, e; };
bool cmp(Act a, Act b) { return a.e < b.e; }
sort(act, act + n, cmp);
int cnt = 0, last = -1;
for (int i = 0; i < n; i++)
    if (act[i].s >= last) { cnt++; last = act[i].e; }
cout << cnt;
生活小例子:每次挑最大的苹果

贪心=每次都挑当前看起来最好的:活动安排就选"结束最早的",腾出最多时间。但要小心:不是所有问题"每次最优"都对,要先证明局部最优能推出全局最优。

易错点:① 贪心 = 局部最优递推出全局最优;② 典型:活动安排、部分背包、硬币找零(特定面额);③ 注意证明贪心正确性。

五级模拟题第五级知识

选择题gcd(48, 36) 的结果是?
A. 6B. 12C. 18D. 24
答案:B
48%36=12,36%12=0 → gcd=12。
选择题n 个有序元素的二分查找最多比较几次?
A. nB. n/2C. ⌈log₂n⌉+1D. 1
答案:C
每次砍半,约 log₂n+1 次。
选择题埃氏筛的时间复杂度约为?
A. O(n)B. O(n log log n)C. O(n²)D. O(log n)
答案:B
埃氏筛 O(n log log n),线性筛 O(n)。
编程题用高精度求两个大整数的和。
答案:参考思路见解析
// 倒序存数组,逐位相加,满十进一
int c[505]={0};
for (int i=0;i<len;i++){ c[i]+=a[i]+b[i]; c[i+1]+=c[i]/10; c[i]%=10; }
个位对齐、逐位加、处理进位。
编程题判断 n 是否为素数(n≤10⁹)。
答案:参考程序见解析
bool ok = (n >= 2);
for (int i=2; i*i<=n; i++)
    if (n % i == 0) { ok = false; break; }
试到 √n 即可。
6

六级 · 数据结构与搜索

树 · 哈夫曼 · 完全二叉树 · 二叉排序树 · DFS/BFS · 简单DP/背包 · 面向对象 · 栈队列
能力目标:掌握树与搜索算法、简单动态规划、面向对象,会使用栈队列核心:树、搜索、背包、类、栈队列

6.1 树的基本概念与遍历第六级知识

① 树的定义

  • 树:n 个结点的分层结构,唯一根,每个结点可有多个孩子;
  • 叶子=没有孩子的结点,深度=层数;
  • 二叉树的每个结点最多 2 个孩子(左、右)。

② 二叉树遍历

C++
struct Node { int v; Node *l, *r; };
void preorder(Node *p) {        // 前序:根-左-右
    if (!p) return;
    cout << p->v << " "; preorder(p->l); preorder(p->r);
}
void inorder(Node *p) {         // 中序:左-根-右
    if (!p) return;
    inorder(p->l); cout << p->v << " "; inorder(p->r);
}
void postorder(Node *p) {       // 后序:左-右-根
    if (!p) return;
    postorder(p->l); postorder(p->r); cout << p->v << " ";
}
易错点:① 一棵树只有一个根;② 前序=根左右、中序=左根右、后序=左右根;③ 已知中序+前序(或后序)可唯一确定二叉树。

6.2 哈夫曼树与哈夫曼编码第六级知识

① 哈夫曼树的构造

选两个权值最小的结点合并为新结点(权值=和)重复直到只剩一棵树

例:权值 {2,3,4,7}:先合 2+3=5 → {4,5,7},再合 4+5=9 → {7,9},再合 7+9=16。带权路径长度最小。

② 哈夫曼编码

左 0 右 1,频率高的字符路径短(编码短),是前缀编码(任何编码不是另一个的前缀),可无歧义解码。

易错点:① 每次挑两个最小合并;② 叶子 n 个,总结点 2n-1;③ 高频字符编码短 → 数据压缩。

6.3 完全二叉树与二叉排序树第六级知识

① 完全二叉树

除最后一层外全满,最后一层靠左。数组存储:i 的左孩子 2i、右孩子 2i+1、父亲 i/2。

② 二叉排序树 BST

C++
Node* insert(Node *p, int x) {
    if (!p) { p = new Node(); p->v = x; return p; }
    if (x < p->v) p->l = insert(p->l, x);
    else p->r = insert(p->r, x);
    return p;
}
// BST 中序遍历 = 有序输出
易错点:① 完全二叉树用数组存,下标规律记住;② BST:左小右大,中序遍历有序;③ 退化链状 → O(n),需平衡。

6.4 哈夫曼编码与格雷编码第六级知识

① 格雷编码

格雷码:相邻两个数的二进制只有一位不同。用途:旋转编码器、信号防错。

C++
// 十进制 n 转格雷码:g = n ^ (n >> 1)
for (int i = 0; i < 8; i++) {
    int g = i ^ (i >> 1);
    cout << i << "->" << g << endl;
}
// 0->0 1->1 2->3 3->2 4->6 5->7 6->5 7->4
易错点:① 格雷码相邻只有 1 位变化;② 转换公式 g = n ^ (n>>1);③ 与二进制是"逐位"差异。

6.5 深度优先搜索 DFS第六级知识

① DFS

C++
bool vis[1005];
vector<int> g[1005];
void dfs(int u) {
    vis[u] = true;
    cout << u << " ";
    for (int v : g[u])
        if (!vis[v]) dfs(v);
}
生活小例子:迷宫探路

DFS=迷宫探险家一条道走到黑,撞墙回头(回溯)换一条,走过的路做记号(vis)防绕圈。用递归实现最自然。

易错点:① DFS 用递归或栈;② 必须有 vis 标记;③ 适合求所有方案、连通块、全排列。

6.6 广度优先搜索 BFS第六级知识

① BFS

C++
#include <queue>
queue<int> q;
bool vis[1005];
vector<int> g[1005];
void bfs(int s) {
    q.push(s); vis[s] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        cout << u << " ";
        for (int v : g[u])
            if (!vis[v]) { vis[v] = true; q.push(v); }
    }
}
生活小例子:水波一圈圈扩散

BFS=往池塘扔石子,水波一圈圈外扩:先到的地方离起点最近。用队列实现(先来先出)。找最短步数最合适!

易错点:① BFS 用队列、DFS 用递归/栈;② BFS 首次到达 = 最短距离;③ 入队时就要标记 vis。

6.7 简单动态规划第六级知识

① 一维 DP:爬楼梯

C++
long long dp[100];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
    dp[i] = dp[i - 1];
    if (i >= 2) dp[i] += dp[i - 2];
}
cout << dp[n];

② 0-1 背包

C++
int W, n, w[105], v[105], dp[10005];
cin >> W >> n;
for (int i = 1; i <= n; i++) cin >> w[i] >> v[i];
for (int i = 1; i <= n; i++)
    for (int j = W; j >= w[i]; j--)      // 倒序!
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
cout << dp[W];
生活小例子:出游装箱

0-1 背包=10kg 的箱子,每件物品只能带一个,怎么装最值钱?dp[j]=容量 j 的最大价值。倒序循环保证每件物品只装一次!

易错点:① DP 三要素:状态、转移方程、初始值;② 0-1 背包倒序,完全背包正序;③ dp 数组大小开到容量 W。

6.8 面向对象:类第六级知识

① 类与对象

C++
class Dog {
public:
    Dog(string n, int a) { name = n; age = a; }   // 构造函数
    void bark() { cout << name << " 汪汪!" << endl; }
private:
    string name;      // 封装:私有成员
    int age;
};
int main() {
    Dog d("旺财", 3);
    d.bark();       // 旺财 汪汪!
    return 0;
}

② 三大特性

  • 封装:private 隐藏数据,通过 public 方法访问;
  • 继承:子类继承父类属性和方法;
  • 多态:同一接口不同实现(虚函数 virtual)。
易错点:① 构造函数与类同名、无返回值;② private 成员外部不能直接访问;③ C++ 中"类"和"结构体"相近,区别是默认访问权限。

6.9 栈、队列、循环队列第六级知识

① 栈(后进先出)

C++
#include <stack>
stack<int> st;
st.push(1); st.push(2);
cout << st.top();   // 2(栈顶)
st.pop();           // 弹出

② 队列(先进先出)

C++
#include <queue>
queue<int> q;
q.push(1); q.push(2);
cout << q.front();  // 1
q.pop();            // 出队

循环队列:数组实现时队尾回绕到队首,避免假溢出。可用 STL deque 双端队列。

生活小例子:叠盘子与排队

=食堂叠盘子后放先拿(后进先出);队列=排队打饭先来先走(先进先出)。栈常用于括号匹配、函数调用,队列常用于 BFS、排队模拟。

易错点:① 栈 LIFO:push/top/pop;② 队列 FIFO:push/front/pop;③ 数组模拟循环队列:下标 (tail+1)%size。

六级模拟题第六级知识

选择题二叉树中序遍历的顺序是?
A. 根-左-右B. 左-根-右C. 左-右-根D. 根-右-左
答案:B
中序=左根右。
选择题0-1 背包的容量循环方向是?
A. 正序B. 倒序C. 任意D. 不循环
答案:B
倒序保证每件物品只取一次。
选择题BFS 用哪种数据结构?
A. 栈B. 队列C. 堆D. 数组
答案:B
BFS 用队列(先进先出)。
选择题格雷码的特点是?
A. 每位都是 1B. 相邻编码只有一位不同C. 编码递增D. 无规律
答案:B
格雷码相邻两个数二进制只有 1 位不同。
编程题用栈判断括号序列是否匹配。
答案:参考思路见解析
stack<char> st;
for (char c : s) {
    if (c=='(' || c=='[') st.push(c);
    else {
        if (st.empty()) { 不匹配; break; }
        char t = st.top(); st.pop();
        if (不配对) { 不匹配; break; }
    }
}
// 最后 st 空则匹配
左括号入栈,右括号与栈顶配对。
编程题用 BFS 求迷宫最短步数。
答案:参考思路见解析
queue 存 (x,y,step)
while (q非空) {
    取队首; 若到终点输出 step;
    四个方向扩展,可走且未访问则入队并记步数
}
第一次到达终点的步数即最短。
7

七级 · 动态规划与图论

数学库函数 · 复杂DP · 图的定义与遍历 · flood fill · 哈希表
能力目标:掌握复杂动态规划、图论基础算法与哈希表核心:复杂DP、图、哈希

7.1 数学库常用函数第七级知识

① 三角 / 对数 / 指数

C++
#include <cmath>
sin(M_PI / 2)      // 1  正弦(弧度)
cos(0)             // 1  余弦
log10(1000)        // 3  以 10 为底对数
log2(8)            // 3  以 2 为底对数
exp(1)             // e ≈ 2.718
pow(2, 10)         // 1024
易错点:① 三角函数用弧度;② log10/log2 是两种对数;③ exp(x)=eˣ。

7.2 复杂动态规划第七级知识

① 最长上升子序列 LIS

C++
int a[1005], dp[1005];
for (int i = 0; i < n; i++) {
    dp[i] = 1;
    for (int j = 0; j < i; j++)
        if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
}
int ans = 0;
for (int i = 0; i < n; i++) ans = max(ans, dp[i]);
cout << ans;   // O(n^2)

② 最长公共子序列 LCS

C++
string s1 = "abcde", s2 = "ace";
int dp[1005][1005];
for (int i = 1; i <= s1.size(); i++)
    for (int j = 1; j <= s2.size(); j++) {
        if (s1[i-1] == s2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
        else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
    }
cout << dp[s1.size()][s2.size()];   // 3

③ 区间 DP 与滚动数组

  • 区间 DP:状态是"区间 [i,j]",如石子合并:dp[i][j] = min(dp[i][k]+dp[k+1][j]) + cost
  • 滚动数组:只保留上一行,把空间从 O(n²) 降到 O(n)。
易错点:① LIS:dp[i]=以 a[i] 结尾的最长上升长度;② LCS 二维 DP 状态转移要记牢;③ 滚动数组 = 循环使用两行。

7.3 图的定义与遍历第七级知识

① 图的定义

  • 图 = 顶点 + 边;有向图(单向)、无向图(双向);
  • 顶点的:无向图=相连边数,有向图分入度/出度
  • 存储方式:邻接矩阵(二维数组)、邻接表(vector 套娃)。

② 存储与遍历

C++
vector<int> g[1005];   // 邻接表
g[u].push_back(v);       // 加边 u→v
// DFS / BFS 遍历与树相同,加 vis 防重复
易错点:① 邻接表省空间,邻接矩阵查边快;② 有向图遍历只走指向方向;③ 图的 DFS/BFS 都靠 vis 去重。

7.4 泛洪算法 flood fill第七级知识

① 连通块计数

C++
int a[105][105], n, m;
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
void flood(int x, int y) {
    a[x][y] = 0;                       // 淹掉(标记访问)
    for (int k = 0; k < 4; k++) {
        int nx = x + dx[k], ny = y + dy[k];
        if (nx >= 0 && nx < n && ny >= 0 && ny < m && a[nx][ny] == 1)
            flood(nx, ny);
    }
}
int cnt = 0;
for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        if (a[i][j] == 1) { cnt++; flood(i, j); }
cout << cnt;
生活小例子:给孤岛编号

flood fill=看小岛地图,从一块陆地出发把相连整片染色(访问=淹掉),数出共有几座岛。DFS/BFS 都行,关键是整片一起处理、走过的标记掉

易错点:① 连通块 = 相互可达的点集;② 访问过的格子立即标记;③ 四方向/八方向按题意选。

7.5 哈希表第七级知识

① 哈希思想

哈希函数把键映射到数组下标,实现平均 O(1) 的插入/查找。冲突用链地址法开放寻址法解决。

C++
#include <unordered_map>
#include <unordered_set>
unordered_map<string, int> mp;
mp["apple"] = 3;                 // 插入
cout << mp["apple"];             // 3  O(1)
unordered_set<int> st;
st.insert(5);
cout << st.count(5);             // 1 判断存在
生活小例子:按首字母分格的储物架

哈希表=按首字母分格的储物架:找 "apple" 直接去 A 格,不用一格格翻。哈希函数帮你算好位置,冲突=两个词撞进同格,用"链子"串起来。C++ 的 unordered_map 底层就是哈希表。

易错点:① 平均 O(1),最坏 O(n);② 无序,需要有序用 map(红黑树 O(log n));③ 空间换时间的典型。

七级模拟题第七级知识

选择题log2(16) 的值是?
A. 2B. 4C. 8D. 16
答案:B
2⁴=16 → log2(16)=4。
选择题LIS 中 dp[i] 表示?
A. 前缀和B. 以 a[i] 结尾的最长上升子序列长度C. 全局最长D. 数组长度
答案:B
经典定义:以 a[i] 结尾的 LIS 长度。
选择题有向图中,从顶点出发的边数称为?
A. 入度B. 出度C. 度D. 深度
答案:B
有向图:出边数=出度,入边数=入度。
编程题用 unordered_map 统计字符出现次数。
答案:参考程序见解析
unordered_map<char,int> mp;
for (char c : s) mp[c]++;
遍历字符串逐字符计数。
8

八级 · 综合与优化

计数原理 · 排列组合 · 杨辉三角 · 倍增 · 代数几何 · 最小生成树 · 最短路 · 时空分析
能力目标:掌握组合数学、图论综合应用与算法优化核心:组合、图论综合、优化

8.1 计数原理:加法与乘法第八级知识

① 加法原理 / 乘法原理

  • 加法原理(分类,互斥):做一件事有 m 种"或"n 种方法 → m+n;
  • 乘法原理(分步,连续):分两步各有 m、n 种 → m×n。
生活小例子:点餐两种算法

套餐 A 或 B 任选=加法(2 种);主食+饮料各选一个=乘法(3×2=6 种)。加"或"、乘"和"——分类相加、分步相乘!

易错点:① 关键词"或"→加法,"再/然后"→乘法;② 分类不能重叠;③ 分步之间独立。

8.2 排列与组合第八级知识

① 排列 P(n,k)(讲究顺序)

从 n 个不同元素取 k 个排成一列P(n,k) = n!/(n-k)!。例:3 人排队选 2 人 P(3,2)=6。

② 组合 C(n,k)(不讲究顺序)

取 k 个成一组C(n,k) = n!/(k!(n-k)!)。例:3 人选 2 人 C(3,2)=3。

C++
// 用递推求组合数 C(n,k)(杨辉三角法)
long long C[50][50];
for (int i = 0; i <= n; i++) {
    C[i][0] = C[i][i] = 1;
    for (int j = 1; j < i; j++)
        C[i][j] = C[i-1][j-1] + C[i-1][j];   // 防溢出
}
易错点:① 排列看顺序、组合不看顺序;② C(n,k)=C(n,n-k);③ 大组合数用递推(杨辉三角)避免溢出。

8.3 杨辉三角第八级知识

① 杨辉三角

C++
int t[20][20];
for (int i = 0; i < n; i++) {
    t[i][0] = t[i][i] = 1;          // 两边是 1
    for (int j = 1; j < i; j++)
        t[i][j] = t[i-1][j-1] + t[i-1][j];   // 肩上两数相加
}
// 第 i 行第 k 个数 = C(i, k)
生活小例子:搭人墙

杨辉三角像人墙:最边上是 1 个人,中间每个人站在下面两个人肩膀上(肩上两数相加)。每行数字正好是组合数

易错点:① 两边 1、中间 = 左上+右上;② 与组合数一一对应;③ 递推生成不溢出。

8.4 倍增法第八级知识

① 倍增的思想

倍增:每次按 2 的幂(1,2,4,8…)跳,把 O(n) 优化到 O(log n)。应用:快速幂、倍增求 LCA、ST 表、稀疏表

C++
// 快速幂:a^b % m
long long qpow(long long a, long long b, long long m) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}
// qpow(2, 10, 1e9+7) = 1024
生活小例子:跳格子省时间

一格一格跳 1000 步太慢。倍增=先大步跳 512、256、128…(二进制拆分:1000=512+256+128+64+32+8),只跳几次就到位

易错点:① 倍增基于二进制拆分;② 快速幂:底数平方、指数除 2;③ 复杂度 O(log n)。

8.5 代数与平面几何(初中数学)第八级知识

① 一元一次 / 二元一次方程

C++
// 解 ax + b = 0
double a = 2, b = -8;
cout << -b / a << endl;        // 4
// 解方程组(克莱默法则):a1x+b1y=c1, a2x+b2y=c2
double a1=1,b1=1,c1=5, a2=2,b2=-1,c2=1;
double det = a1*b2 - a2*b1;                 // 行列式
double x = (c1*b2 - c2*b1) / det;           // 2
double y = (a1*c2 - a2*c1) / det;           // 3
cout << x << " " << y;

② 平面图形面积

图形公式
×
易错点:① det≠0 才有唯一解;② 面积公式:三角÷2、圆 πr²;③ π 用 acos(-1) 或常量。

8.6 最小生成树与最短路第八级知识

① Kruskal(选最小边 + 并查集)

C++
struct Edge { int u, v, w; };
bool cmp(Edge a, Edge b) { return a.w < b.w; }
int fa[1005];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
sort(edges.begin(), edges.end(), cmp);
long long ans = 0;
for (auto &e : edges) {
    int fu = find(e.u), fv = find(e.v);
    if (fu != fv) { fa[fu] = fv; ans += e.w; }   // 不成环就选
}

② Prim(加点法)

从一个点出发,每次选连接已选集合的最短边加入新点,重复 n-1 次。适合稠密图。

③ Dijkstra(单源最短路)

C++
#include <queue>
vector<pair<int,int>> g[1005];
long long dist[1005];
priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<>> pq;
dist[s] = 0; pq.push({0, s});
while (!pq.empty()) {
    auto [d, u] = pq.top(); pq.pop();
    if (d > dist[u]) continue;
    for (auto [v, w] : g[u])
        if (dist[u] + w < dist[v]) {
            dist[v] = dist[u] + w;
            pq.push({dist[v], v});
        }
}

④ Floyd(全源最短路,O(n³))

C++
for (int k = 1; k <= n; k++)
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (d[i][k] + d[k][j] < d[i][j])
                d[i][j] = d[i][k] + d[k][j];
易错点:① Kruskal 用并查集判环;② Dijkstra 用优先队列,边权不能为负;③ Floyd 三重循环,中间点 k 在最外层。

8.7 算法时空效率分析与优化第八级知识

① 时空效率分析

  • 时间:大 O 表示增长趋势;空间:额外内存;
  • 常见:排序 O(n²)/O(n log n)、查找 O(n)/O(log n)、遍历 O(n)、DP 看状态×转移。

② 算法优化

  • 数学优化:等差数列求和公式 n(n+1)/2 代替循环;
  • 数据结构优化:哈希、前缀和、ST 表、线段树;
  • 剪枝:搜索提前排除不可能分支;
  • 空间换时间:预处理缓存;
  • 二分/倍增:把线性变成对数。
生活小例子:数台阶两种算法

数 1+2+…+100:循环加走 100 步,数学公式 100×101÷2 一步算完——数学优化用公式省掉大量循环!

易错点:① 先分析复杂度判断能否过题;② 等差/等比求和公式是经典优化;③ 权衡时间与空间。

八级模拟题第八级知识

选择题从 A 到 B 有 3 条路、B 到 C 有 2 条路,A 经 B 到 C 有几种走法?
A. 5B. 6C. 3D. 2
答案:B
分步乘法:3×2=6。
选择题C(5,2) 的值是?
A. 10B. 20C. 5D. 15
答案:A
C(5,2)=5×4/2=10。
选择题快速幂求 a^b 的复杂度是?
A. O(b)B. O(log b)C. O(1)D. O(b²)
答案:B
每次指数减半 → O(log b)。
选择题Kruskal 算法判断"是否成环"用到的结构是?
A. 栈B. 并查集C. 队列D. 哈希
答案:B
并查集(union-find)快速判环。
编程题用 Kruskal 求最小生成树总权值。
答案:参考思路见解析
// 边按权排序 + 并查集
sort(edges, cmp);
for (边 e) if (find(u)!=find(v)) { 合并; ans+=w; }
选最小边、不成环就加。
编程题用快速幂计算 a^b mod m。
答案:参考程序见解析
long long qpow(long long a, long long b, long long m){
    long long r=1;
    while(b){ if(b&1) r=r*a%m; a=a*a%m; b>>=1; }
    return r;
}
二进制拆分指数,边平方边取模。