ARTICLE · 1085878
GESP 2026年9月 C++ 七级真题,答案与知识点解析
一、单选题(每题 2 分,共 30 分)
第 1 题
下列 C++ 代码的输出结果是( )。
#include <iostream>
using namespace std;
int main() {
int a = 5, b = 3;
cout << (a & b) + (a | b) << endl;
return 0;
}
A. 6
B. 7
C. 8
D. 9
答案:C
知识点解析
本题考查按位与、按位或运算。将 5 写成二进制 101,3 写成 011:按位与得 001(即 1),按位或得 111(即 7),输出为 1 + 7 = 8。这里用到了恒等式 a + b = (a & b) + (a | b):与运算是“公共部分只算一次”,或运算是“合并部分都算一次”,两者相加恰好等于 a + b = 8。选项 B(7)只是 a|b 的中间结果,选项 A(6)恰为 a^b = 101^011 = 110 的值,均不是最终答案。
第 2 题
使用 cmath 或 math.h 中的数学库函数,下列说法中正确的是( )。
A. pow(2, 3) 的返回值类型为 int
B. sin(30) 的参数 30 表示 30 度
C. sqrt(4) 的返回值类型为 int
D. log(1) 的返回值为 0.0,且类型为 double
答案:D
知识点解析
本题考查 cmath 数学库函数的参数含义与返回值类型。pow(2, 3) 和 sqrt(4) 的返回值类型都是 double 而非 int,故 A、C 错误;sin、cos 等三角函数的参数是弧度而不是角度,传入 30 表示 30 弧度,B 错误;log 是自然对数,log(1) = ln 1 = 0.0,且所有浮点函数返回值类型均为 double,D 正确。
第 3 题
有 4 个字符,出现次数分别为 1、2、3、4。构造哈夫曼树后,出现次数为 1 的字符的哈夫曼编码长度为( )。
A. 1
B. 2
C. 3
D. 4
答案:C
知识点解析
本题考查哈夫曼树的构造过程。哈夫曼树每次取出权值(出现次数)最小的两棵树合并:先合并 1 和 2 得 3,再合并 3 和 3 得 6,最后合并 6 和 4 得 10。出现次数为 1 的字符在第一次合并时就与权 2 的字符结合,位于树的最深层,从根到它的路径经过 3 条边,所以编码长度为 3。规律是:越早被合并(权值越小)的叶子越深,编码越长。
第 4 题
从 4 × 5 个点连接成的网格的左上角走到右下角,每次只能向右或向下移动,不同的路径共有( )条。
A. 20
B. 35
C. 70
D. 126
答案:B
知识点解析
本题考查组合计数的网格路径模型。4 × 5 个点构成的网格,从左上角走到右下角需要向下 3 步、向右 4 步,共 7 步。路径由这 7 步的排列顺序决定,只需在 7 步中选出哪 3 步向下(其余向右),共有 C(7, 3) = 35 条。选项 C 的 70 = C(8, 4)、D 的 126 = C(9, 4) 分别对应更大的点阵,是把步数算多后的结果。
第 5 题
在含有 n 个结点的二叉排序树中查找一个元素,平均时间复杂度和最坏时间复杂度分别为( )。
A. O(log n)、O(n)
B. O(n)、O(log n)
C. O(log n)、O(log n)
D. O(1)、O(n)
答案:A
知识点解析
本题考查二叉排序树(BST)的查找效率分析。当树形比较平衡(接近完全二叉树)时,每次比较都能排除一半结点,平均查找长度为 O(log n);但当插入顺序不当(例如按有序序列依次插入)时,树会退化成一条链,深度为 n − 1,最坏查找需要 O(n)。注意 BST 不像平衡树那样自带平衡机制,所以最坏情形无法保证 O(log n)。
第 6 题
有 4 堆石子,数量分别为 1、2、3、4。每次可以合并相邻两堆,合并代价为两堆石子数之和。将所有石子合并成一堆的最小总代价为( )。
A. 17
B. 19
C. 20
D. 23
答案:B
知识点解析
本题考查区间 DP 思想(石子合并问题),只能合并相邻两堆。每合并一次,代价为两堆之和,等价于每堆石子在每次包含它的合并中都被累加一次,因此让“大堆”尽量晚参与合并更优。最优方案:先并 1、2(代价 3),得 3, 3, 4;再并 3、3(代价 6),得 6, 4;最后并 6、4(代价 10),总代价 3 + 6 + 10 = 19。若先合并 3、4(代价 7),总代价为 7 + 3 + 10 = 20(选项 C)。要注意“相邻最小”的贪心只是在本题数据下恰好取得最优解,石子合并的一般情形仍需用区间 DP 求解。
第 7 题
在无权图中,使用 BFS 从起点开始遍历,并在访问由结点 u 扩展的相邻结点 v 时记录 dist[v] = dist[u] + 1,且起点的 dist 为 0,则最终 dist[v] 表示的是( )。
A. 起点到结点 v 的最少边数
B. 结点 v 的度数
C. 从起点到结点 v 的路径上经过的最大边权
D. 包含结点 v 的连通块大小
答案:A
知识点解析
本题考查 BFS 在无权图最短路中的应用。BFS 借助队列按“层”扩展:先访问距起点 1 条边的结点,再访问 2 条边的结点……因此每个结点第一次被访问(标记 vis)时,对应的 dist[v] 就是起点到 v 的最少边数。选项 B 的度数、D 的连通块大小与遍历顺序无关,选项 C 涉及“最大边权”,而本图是无权图,边权概念不存在。
第 8 题
在二维网格上实现泛洪填充时,为了防止递归层数过深,最适合的非递归实现方式是( )。
A. 使用哈希表记录每个格子被访问的次数
B. 使用快速排序预处理网格
C. 使用二分查找定位边界
D. 使用队列实现 BFS 或使用显式栈模拟 DFS
答案:D
知识点解析
本题考查搜索的非递归实现。递归 DFS 的“层次”保存在系统调用栈上,网格很大时递归深度可达格子总数,容易栈溢出。改用显式队列(BFS)或显式栈(模拟 DFS),把待访问格子存放在堆上的数据结构中,即可避免递归层数过深的问题。选项 A、B、C 与防止递归过深没有任何关系,属于无关干扰项。
第 9 题
关于哈希表,下列说法正确的是( )。
A. 只要哈希函数选择合适,就可以完全避免冲突
B. 在链地址法中,查找一个元素的时间复杂度一定为 O(1)
C. 开放定址法发生冲突后,会在表内寻找下一个可用位置
D. 哈希表的查找速度与表中元素个数无关
答案:C
知识点解析
本题考查哈希表的冲突处理方法。开放定址法的特点是:所有元素都存放在表内,发生冲突时按探测序列(线性探测、平方探测等)在表内继续寻找下一个可用位置,C 正确。A 错误:无论哈希函数多好,只要 key 的取值空间大于表长,冲突就不可避免;B 错误:链地址法中同一槽位链表过长时查找退化为 O(链长);D 错误:元素越多装填因子越大,冲突越频繁,查找速度越慢。
第 10 题
下列 C++ 代码的输出结果是( )。
#include <iostream>
using namespace std;
void inc(int &x) {
x++;
}
int main() {
int a = 3;
inc(a);
cout << a;
return 0;
}
A. 3
B. 4
C. 5
D. 编译错误
答案:B
知识点解析
本题考查引用传参。void inc(int &x) 中的 x 是实参 a 的别名(引用),函数内 x++ 直接修改的就是 main 中的 a,因此调用后 a = 4。如果把参数写成值传递 int x,则函数内修改的是副本,输出仍为 3。选项 D 错误:引用传参是合法的 C++ 语法,不会编译错误。
第 11 题
用动态规划求两个序列 a₁ 和 a₂ 的最长公共子序列长度,若 dp[i][j] 表示 a₁ 前 i 个元素与 a₂ 前 j 个元素的 LCS 长度。当 a₁[i - 1] == a₂[j - 1] 时,正确的状态转移是( )。
A. dp[i][j] = dp[i - 1][j - 1] + 1
B. dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
C. dp[i][j] = dp[i - 1][j] + 1
D. dp[i][j] = dp[i][j - 1]
答案:A
知识点解析
本题考查最长公共子序列(LCS)的状态转移方程。当两序列末位字符相等(a₁[i−1] == a₂[j−1])时,这对字符可以直接配成 LCS 的最后一个字符,于是 dp[i][j] = dp[i−1][j−1] + 1。选项 B 的 max(dp[i−1][j], dp[i][j−1]) 是字符不相等时的转移;选项 C、D 的转移只消去一个序列的一个字符,没有把相等字符“配对”,会漏掉最优解。
第 12 题
下列代码是一维数组优化 0/1 背包的核心片段,执行后 dp[8] 的输出结果是( )。
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int w = 3, v = 5, W = 8;
int dp[9] = {0};
for (int c = W; c >= w; c--)
dp[c] = max(dp[c], dp[c - w] + v);
cout << dp[8] << endl;
return 0;
}
A. 0
B. 1
C. 3
D. 5
答案:D
知识点解析
本题考查 0/1 背包的一维(滚动数组)优化,核心是容量循环必须倒序。程序中只有一个物品,重量 w = 3、价值 v = 5,背包容量 W = 8。c 从 8 递减到 3:当 c = 8 时,dp[8] = max(dp[8], dp[5] + 5) = 5(此时 dp[5] 还是 0,对应“只放这一个物品”)。倒序枚举保证 dp[c − w] 还是上一行(未考虑本物品)的值,物品不会被重复选取;若正序枚举则变成完全背包,同一物品可放多次。
第 13 题
若要求排序后相等元素的相对顺序保持不变,下列排序算法中最不适宜使用的是( )。
A. 冒泡排序
B. 插入排序
C. 归并排序
D. 快速排序
答案:D
知识点解析
本题考查排序算法的稳定性。稳定排序要求排序后相等元素的相对顺序保持不变:冒泡排序相邻交换时遇到相等元素不交换;插入排序把元素插到相等元素之后;归并排序合并时优先取左半部分元素——三者都是稳定的。快速排序的划分(Partition)过程依赖远距离交换,相等元素可能被交换到彼此之前,因此是不稳定的,最不适宜用于本题要求。
第 14 题
下列代码片段的时间复杂度为( )。
long long s = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j += i)
s += i + j;
A. O(n)
B. O(n log n)
C. O(n²)
D. O(n√n)
答案:B
知识点解析
本题考查嵌套循环的复杂度分析(调和级数求和)。内层循环 j 从 1 每次 += i,执行次数约为 ⌈n / i⌉,总次数为 n/1 + n/2 + … + n/n = n(1 + 1/2 + … + 1/n)。括号内是调和级数,其和约为 ln n,所以总时间复杂度为 O(n log n)。选项 C 的 O(n²) 是把内层误当作每次都跑满 n 次,选项 D 的 O(n√n) 是另一种常见的错误估计。
第 15 题
已知 int a[6] = {1, 3, 5, 7, 9, 11}; int *p = a + 1;,则表达式 *(p + 3) 的值是( )。
A. 5
B. 7
C. 9
D. 11
答案:C
知识点解析
本题考查指针算术。p = a + 1 使 p 指向 a[1](值为 3),指针加法按元素个数移动:p + 3 指向 a[1 + 3] = a[4],其值为 9。容易混淆的是 a[3](值为 7,选项 B),那是把 p 当作 a 起点算的;实际上 p 已经偏移了 1 个元素。选项 A(5)和 D(11)分别是 a[2] 和 a[5] 的值。
二、判断题(每题 2 分,共 20 分)
第 1 题
使用 cmath 或 math.h 中的函数,表达式 exp(0) 的结果值为 1.0,且类型为 double。
答案:√
知识点解析
本题考查指数函数 exp 的求值与返回类型。exp(x) 计算 e^x,而 e⁰ = 1,所以 exp(0) 的值为 1.0;cmath 中的浮点函数统一返回 double 类型。该说法正确。
第 2 题
采用开放定址法处理冲突的哈希表中,删除一个元素后可以直接将该位置置空,不会影响后续查找。
答案:×
知识点解析
本题考查开放定址法的删除操作。开放定址法中,后续元素的查找依赖探测序列:若把某位置直接置空,探测遇到空位就会提前判定“元素不存在”,从而截断探测链,导致存放在其后的同义词无法被找到。正确做法是惰性删除——给该位置打上“已删除”标记,查找时跳过但不终止探测。
第 3 题
在哈夫曼树中,出现次数更多的叶子结点,其深度总是更小。
答案:×
知识点解析
本题考查哈夫曼树的性质。哈夫曼树只保证“频率高的叶子深度不超过频率低的叶子”,即深度与频率成反序关系,但不保证严格更小。反例:四个字符频率为 2、2、3、3 时,合并 2+2 = 4、3+3 = 6、4+6 = 10,四个叶子深度都是 2,频率 3 的叶子并不比频率 2 的更浅。
第 4 题
在一个有向图中,所有顶点的入度之和总是等于所有顶点的出度之和。
答案:√
知识点解析
本题考查有向图的度数性质。每条有向边恰好从一个顶点出发(贡献 1 个出度)、进入一个顶点(贡献 1 个入度),因此所有顶点的入度之和与出度之和都等于图的边数 m,二者必然相等。
第 5 题
广度优先搜索通常借助队列实现,深度优先搜索通常借助栈或递归实现。
答案:√
知识点解析
本题考查两种图遍历的实现方式。BFS 要求“先发现的先扩展”,队列的先进先出特性恰好满足;DFS 要求“一条路走到黑再回溯”,递归调用(隐式使用系统栈)或显式栈的后进先出特性恰好满足。该说法正确。
第 6 题
快速排序的平均时间复杂度为 O(n log n),最坏时间复杂度也为 O(n log n)。
答案:×
知识点解析
本题考查快速排序的复杂度。快排的平均时间复杂度确实是 O(n log n),但在最坏情形下(如序列已有序而每次又固定取首元素为基准),每次划分只能切出一个元素,递归深度达 n,退化为 O(n²)。正确说法是:最坏时间复杂度为 O(n²)。
第 7 题
为解决 0/1 背包问题,使用一维数组优化时,内层容量循环应从大到小枚举。
答案:√
知识点解析
本题考查 0/1 背包一维优化的枚举方向。二维转移 dp[i][c] 依赖的是第 i − 1 行的 dp[i−1][c − w],一维化后必须保证更新 dp[c] 时 dp[c − w] 还是“上一行”的旧值。容量从大到小枚举时,dp[c − w] 尚未被本轮更新,正确;若从小到大枚举,同一物品可能被重复选取,就变成完全背包了。
第 8 题
使用邻接表存储图时,遍历某个顶点的所有邻边所需时间与图中顶点数成正比。
答案:×
知识点解析
本题考查邻接表与邻接矩阵的遍历效率。邻接表只为每个顶点存放其实际拥有的邻边,遍历某顶点全部邻边的时间与该顶点的度数成正比,总遍历为 O(n + m)。与顶点数成正比(O(n))的是邻接矩阵:即使某顶点只有一条邻边,也要扫描一整行。
第 9 题
在按层序从 1 开始对结点编号的完全二叉树中,编号为 i(i > 1)的结点的父结点编号为 ⌊i/2⌋。
答案:√
知识点解析
本题考查完全二叉树的编号性质。层序从 1 开始编号时,结点 i 的左孩子为 2i、右孩子为 2i + 1,反推可知其父结点编号为 ⌊i/2⌋(奇数 i 时为 (i−1)/2,偶数 i 时为 i/2)。这一性质正是二叉堆可以直接用数组存储、通过下标运算访问父子结点的基础。
第 10 题
在定义了数组 int arr[10]; 后,表达式 arr 和表达式 &arr[0] 总是等价的。
答案:×
知识点解析
本题考查数组名与指针的区别。虽然两者的数值(地址)相同,但类型不同:arr 是 int[10] 类型(表达式中退化为 int*),而 &arr[0] 是 int*;取 &arr 得到的是 int(*)[10]。实际差异可以体现出来:sizeof(arr) 为 40(整个数组大小),sizeof(&arr[0]) 为 8(一个指针);且 arr + 1 前进 4 字节,而 (&arr) + 1 前进 40 字节。因此说“总是等价”是错误的。
三、编程题(每题 25 分,共 50 分)
必经之路
时间限制 1.0 s 内存限制 512.0 MB
题目描述
给定一张有 n 个结点 m 条边的有向图 G,G 中的结点依次以 1, 2, ..., n 编号。第 i 条边(1 ≤ i ≤ m)从结点 uᵢ 指向结点 vᵢ。
G 中任一入度为 0 的结点可以作为合法起点,任一出度为 0 的结点可以作为合法终点。
如果 G 中所有可能的从合法起点到合法终点的路径都会经过结点 u,则称 u 是必经点。注意必经点可以为合法起点或合法终点。
请你求出 G 中所有必经点的编号。
例如,在下图中合法起点有点 1 与点 2,合法终点有点 7 与点 8。
(1) (5)---->(7)
\ ^ \ ^
v / v /
(3) / (6)
^ \ / \
/ v / v
(2)---->(4) (8)
所有合法起点到合法终点的路径为:
- 1 → 3 → 4 → 5 → 7
- 1 → 3 → 4 → 5 → 6 → 7
- 1 → 3 → 4 → 5 → 6 → 8
- 2 → 3 → 4 → 5 → 7
- 2 → 3 → 4 → 5 → 6 → 7
- 2 → 3 → 4 → 5 → 6 → 8
- 2 → 4 → 5 → 7
- 2 → 4 → 5 → 6 → 7
- 2 → 4 → 5 → 6 → 8
因此必经点有两个,编号分别为 4, 5。
输入格式
第一行,两个正整数 n, m,表示有向图 G 中的结点数与边数。
接下来 m 行,每行两个正整数 uᵢ, vᵢ,表示一条从结点 uᵢ 指向结点 vᵢ 的有向边。
保证 G 中至少有一个合法起点,至少有一个合法终点,且至少存在一条从一个合法起点到一个合法终点路径,同时不存在孤立点(即出度和入度都为 0 的点)。
输出格式
第一行,一个整数,表示必经点的数量 k。
如果存在必经点,则第二行从小到大输出 G 中所有必经点的编号。
样例
8 9
1 3
2 3
3 4
4 5
5 6
6 7
6 8
2 4
5 7
2
4 5
8 9
1 3
2 3
3 4
4 5
5 6
6 7
6 8
2 5
4 7
0
数据范围
对于 40% 的测试点,保证 1 ≤ n ≤ 100,1 ≤ m ≤ 200。
对于所有测试点,保证 1 ≤ n ≤ 1000,1 ≤ m ≤ 2000。保证 G 中至少有一个合法起点,至少有一个合法终点,且至少存在一条从一个合法起点到一个合法终点路径,同时不存在孤立点(即出度和入度都为 0 的点)。
参考程序(答案)
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1005;
const int E = 2005;
int n, m;
int h[N], to[E], nx[E], et;
int id[N], od[N];
bool vis[N];
int q[N], ql, qr;
int ans[N], cnt;
bool chk(int ban) {
ql = qr = 0;
for (int i = 1; i <= n; i++) {
vis[i] = (i == ban);
if (!vis[i] && !id[i]) {
q[++qr] = i;
vis[i] = 1;
}
}
while (ql < qr) {
int u = q[++ql];
for (int i = h[u]; i; i = nx[i]) {
int v = to[i];
if (vis[v])
continue;
if (!od[v])
return 0;
q[++qr] = v;
vis[v] = 1;
}
}
return 1;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
to[++et] = v;
nx[et] = h[u];
h[u] = et;
od[u]++;
id[v]++;
}
for (int i = 1; i <= n; i++)
if (chk(i))
ans[++cnt] = i;
printf("%d\n", cnt);
for (int i = 1; i <= cnt; i++)
printf("%d%c", ans[i], " \n"[i == cnt]);
return 0;
}
括号序列
时间限制 1.0 s 内存限制 512.0 MB
题目描述
对于字符串 S 与 T,如果从 S 中删除任意多个字符可以得到 T,那么 T 是 S 的子序列。换言之,T 是选取 S 中的若干字符按下标顺序连接而成的。两个子序列不同当且仅当所选取的下标不同。
例如 sun 是 sequence 的子序列,因为从 sequence 中删除 eq、e 和 ce 可以得到 sun;sequence 有 2⁸ 个不同的子序列,其中有空字符串,也有三个不同的子序列 e,因为 sequence 的第 2、5、8 个字符都为 e,分别保留这三个字符得到的子序列是不同的。
对于字符串 S,如果 S 满足以下条件那么 S 是合法括号序列:
- S 是空字符串,或者
- S 可由 (、合法括号序列、) 三者连接得到,或者
- S 可由两个合法括号序列连接得到。
例如 ()、()()、(()) 和 (()()) 都是合法括号序列。但是 (()、)( 不是合法括号序列。
给定一个长度为 n 的仅包含 ( 与 ) 的字符串 S。请你求出 S 所有 2ⁿ 个子序列中有多少个合法括号序列。由于答案可能很大,请你输出答案对 10⁹ 取模的结果。
例如,S 为 ))(()( 时共有 3 个子序列是合法括号序列,分别为空字符串与两个不同的子序列 ()。
输入格式
第一行,一个正整数 n,表示字符串 S 的长度。
第二行,长度为 n 的仅包含 ( 与 ) 的字符串 S。
输出格式
输出一行,一个整数,表示 S 的合法括号子序列的数量对 10⁹ 取模的结果。
样例
6
))(()(
3
34
((((((((((((((((()))))))))))))))))
333606220
数据范围
对于 40% 的测试点,保证 1 ≤ n ≤ 400。
对于所有测试点,保证 1 ≤ n ≤ 2000。
参考程序(答案)
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2005;
const int mod = 1e9;
int n;
char s[N];
int f[N][N];
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n; j++) {
f[i][j] = f[i - 1][j];
if (s[i] == '(' && j)
f[i][j] = (f[i][j] + f[i - 1][j - 1]) % mod;
if (s[i] == ')' && j < n)
f[i][j] = (f[i][j] + f[i - 1][j + 1]) % mod;
}
printf("%d\n", f[n][0]);
return 0;
}
由于工作量较大,若存在错漏欢迎大家评论区指正。祝各位考生顺利通过!觉得有用,欢迎点赞、在看、转发三连。