一、单项选择题(共 15 题)
1. 以下哪个命令会开启编译器几乎所有常用的警告?
A. g++ -g -lm main.cpp -o main
B. g++ -g -Wall main.cpp -o main
C. g++ -g -O2 main.cpp -o main
D. g++ -g -std=c++11 main.cpp -o main
答案: B。-Wall:开启一组常见且重要的编译警告,其中 W 是 warning 的缩写,all 表示“很多常用警告”,但并不是所有警告。-g:生成调试信息,方便用 gdb 等调试器查看源码行号和变量。-lm:链接数学库。-O2:开启二级优化。-std=c++11:指定 C++11 标准。-o:指定输出文件名,例如 -o main 表示把生成的可执行文件命名为 main。 命令顺序:g++ -o main main.cpp 和 g++ main.cpp -o main 都可以,后者在教学和比赛中更常见。
2. 关于 NOI Linux 的文件操作命令,描述错误的是
A. mkdir 是新建一个文件
B. pwd 显示当前工作路径
C. ls 显示文件及文件夹
D. more 分页显示文本文件
答案: A。mkdir:make directory 的缩写,用于创建目录,也就是新建文件夹,不是新建普通文件。pwd:print working directory 的缩写,用于显示当前工作目录。ls:list 的缩写,用于列出当前目录下的文件和文件夹。more:用于分页查看文本文件内容。
3. 以下哪个不属于 algorithm 模板库的操作函数?
A. reverse
B. sort
C. push_back
D. lower_bound
答案: C。reverse:定义在 <algorithm> 中,用于把一个区间内的元素反转。sort:定义在 <algorithm> 中,用于对一个区间排序。lower_bound:定义在 <algorithm> 中,用于在有序区间中查找第一个大于等于目标值的位置。push_back:不是 <algorithm> 中的函数,而是 vector、string 等容器自己的成员函数,用于在末尾加入元素。
4. 以下哪个说法是不正确的?
A. tuple 中的每个值都必须是相同的类型
B. pair 定义在 utility
C. multiset 为有序可重集合
D. set 为有序不可重集合
答案: A。 A:错误。tuple 可以把多个不同类型的值组合在一起,例如 tuple<int, string, double>。 B:正确。pair 定义在 <utility> 中,用于存放两个值,两个值的类型也可以不同。 C:正确。multiset 是有序可重集合,元素会自动排序,并且允许相同元素出现多次。 D:正确。set 是有序不可重集合,元素会自动排序,但相同元素只保留一份。
5. 对下图使用 Floyd 算法计算 S 点到其余各点的最短路径长度时,到 B 点的距离 d[B] 初始时赋为 8。在算法执行过程中不可能出现的值是
第5题图 答案: A。 初始时直接边 S-B 的距离为 8,所以 d[B] 一开始是 8。 经过点 A 时,可以得到路径 S-A-B,长度为 2+5=7,所以 d[B] 可能变成 7。 经过点 D 时,可以得到路径 S-D-B,长度为 3+3=6,所以 d[B] 可能变成 6。 继续利用 D-C-B 这条更短路线,可以得到 S-D-C-B,长度为 3+1+1=5,所以最终最短距离可以到 5。 Floyd 更新只会把距离改成某条实际路径的长度,而图中从 S 到 B 的最短路已经是 5,不可能出现比最短路还小的 4。
6. 平面上有 5 个点 A(3,3)、B(3,5)、C(2,1)、D(1,3)、E(5,1)。以这 5 个点作为完全图 G 的顶点,每两点之间的直线距离为对应边权。图 G 的最小生成树中所有边权之和为
A. 8
B. 7+√5
C. 9
D. 4+√5+2√2
答案: D。
按边权从小到大选边:
AB=2、
AD=2、
AC=√5、
AE=2√2。这 4 条边连接了全部 5 个点,所以最小生成树边权和为
2+2+√5+2√2=4+√5+2√2。
7. 朴素 Dijkstra 的时间复杂度是
A. O(n^2 log n)
B. O(n^2)
C. O(n^3)
D. O(n log n)
答案: B。朴素实现每轮线性找最小值。
8. 对于一棵二叉树,独立集是指两两互不相邻的结点构成的集合。比如图 1 有 5 个不同的独立集,图 2 有 14 个不同的独立集。那么图 3 有多少个不同的独立集。
第8题图 A. 1936
B. 3600
C. 5536
D. 6300
答案: C。 用树形 DP。对每个子树记录两个数:f0 表示“不选当前根结点”的独立集数量,f1 表示“选当前根结点”的独立集数量。 若当前结点的儿子为若干子树 v,则:f0 = Π(f0[v]+f1[v]),因为当前结点不选时,每个儿子可选也可不选。f1 = Πf0[v],因为当前结点被选时,所有儿子都不能选。 叶子结点为 (f0,f1)=(1,1)。沿图 3 左右两边相同形状的子树自底向上计算,可依次得到 (2,1)、(6,2)、(16,6)、(44,16)。 根结点左右两个大子树都是 (44,16),所以不选根有 (44+16)×(44+16)=3600 种,选根有 44×44=1936 种。 总数为 3600+1936=5536。
9. 以下哪个说法是正确的?
A. 判断图连通只能用 BFS
B. 高斯解决了七桥问题
C. 图灵用图灵机破解 Enigma
D. 已知元素集合且表长不少于元素数时一定存在无冲突哈希函数
答案: D。其余三项表述都不准确。
10. 下图中的 8 个结点通过 13 条无向边相连,每条边有安全性标记和长度。从中找出 7 条边使 8 个结点连通,且恰好有 2 条 Safe 边,则这 7 条边长度之和 path 的最小值为
第10题图 答案: C。 一种最优选法是:0-1(D,1)、1-2(S,2)、2-3(D,3)、2-4(S,1)、2-5(D,2)、3-6(D,1)、3-7(D,2)。 其中恰好有 2 条 Safe 边:1-2 和 2-4;7 条边连通全部 8 个结点。 总长度为 1+2+3+1+2+1+2=12。
11. 以下哪个不是 C++ 语言中面向对象模式的主要性质?
答案: C。面向对象三大性质是封装、继承、多态。
12. 从 1 到 2021 的所有奇数中,至少要选出多少个数,才能确保其中必定存在两个数,它们的和是 2022?
答案: C。 从 1 到 2021 一共有 1011 个奇数。两个奇数要相加为 2022,可以配成:1+2021、3+2019、5+2017,一直到 1009+1013。 这样共有 505 对互补的数;中间的 1011 单独剩下,因为 1011+1011=2022,但同一个数不能选两次。 为了避免出现和为 2022 的一对数,每一对中最多只能选 1 个,再加上可以选中间的 1011,最多能安全选 505+1=506 个。 因此一旦选到 507 个,根据抽屉原理,必定有某一对互补数被同时选中,它们的和就是 2022。
13. 如果一棵二叉树有 2024 个结点,树中的结点按层次顺序编号,根结点编号为 1,那么编号为 1025 的结点的父结点是第几层第几个结点。
A. 10,1
B. 10,2
C. 9,256
D. 11,2
答案: A。父结点编号为 1025/2=512,即第 10 层第 1 个。
14. 4 个学生和 2 个老师围绕圆桌入座,且 2 个老师之间至少有 1 个学生,有多少种就座方法?
答案: B。 圆桌排列要先消除旋转重复。先把 4 个学生围成一圈,圆排列数量为 (4-1)!=6。 4 个学生坐好后,学生之间形成 4 个空位。为了保证 2 个老师之间至少有 1 个学生,两个老师不能坐在同一个空位中,只能从 4 个空位中选 2 个不同空位放老师。选空位有 C(4,2)=6 种。 2 个老师本身不同,还可以交换位置,有 2! 种。 所以总数为 (4-1)! × C(4,2) × 2! = 6×6×2=72。
15. 由数字 1,2,3,4,5,6,7 组成无重复数字的 7 位整数,从中任取一个,要求首位不为 1,且任意相邻两位数字之差的绝对值不大于 2,则所取数满足条件的概率为
A. 1/120
B. 1/240
C. 1/360
D. 1/480
答案: A。 所有由 1~7 组成且无重复数字的 7 位数共有 7! 个。 相邻两位数字之差不大于 2,等价于每一步只能从当前数字走到还没用过的 ±1 或 ±2。可以按首位分类回溯统计,并排除首位为 1 的情况。 首位为 2,3,4,5,6,7 时,满足条件的个数分别为 7,5,4,5,7,14,合计 42 个。 所以概率为 42/7! = 42/5040 = 1/120。
二、阅读程序题(共 12 题)
(一)阅读下面的程序代码,回答 16~21 题
代码解读: 这段程序的核心是 树状数组 + 排序 + 二分定位。它先把两组数据按值对应起来,再把问题转化为统计一个序列中的逆序对个数。第 11 行的 x & (-x) 是树状数组中的 lowbit,add 和 sum 分别负责单点修改与前缀统计,因此整题本质是在用 O(n log n) 的方法求“错位数量”。
01 #include <iostream>
02 #include <algorithm>
03 #include <string.h>
04 using namespace std;
05 const int N = 100000, M = 100000;
06 const long long mod = 1e8 - 3;
07 struct StMatch { int a, b; } matches[N + 1];
08 bool operator < (const StMatch a, const StMatch b) { return a.a < b.a; }
09 struct StTreeArray {
10 int tree[M + 1];
11 int lb(int x) { return x & (-x); }
12 void add(int x, int t) {
13 while (x <= M) {
14 tree[x] += t;
15 x += lb(x);
16 }
17 }
18 int sum(int x) {
19 int ans = 0;
20 while (x > 0) {
21 ans += tree[x];
22 x -= lb(x);
23 }
24 return ans;
25 }
26 } t;
27 int n, a[N + 2], b[N + 2], pos[N + 2];
28 int index(int a[], int x) {
29 int l = 1, r = n;
30 while (l < r) {
31 int m = (l + r) >> 1;
32 if (a[m] == x) return m;
33 if (a[m] < x) l = m + 1;
34 else r = m - 1;
35 }
36 if (a[l] == x) return l;
37 if (a[r] == x) return r;
38 return 0;
39 }
40 int main() {
41 cin >> n;
42 ...
43 }
16. 第 11 行返回的是最低位 1 对应的 2 的幂。
答案: A。x & (-x) 返回的正是二进制最低位 1 所对应的数值。
17. 将第 55 行和第 56 行交换,不影响程序结果。
答案: A。两次排序分别作用于数组 a 和 b,彼此独立,交换顺序不影响后续结果。
18. 第 42 行可以去掉,不影响程序结果。
答案: A。二分过程实际上不会走到该分支。
19. 第 58 行可以去掉,不影响程序结果。
答案: A。后面只依赖 matches[i].b 和 pos。
20. 本程序的时间复杂度是
A. O(n^2 log n)
B. O(log n)
C. O(n)
D. O(n log n)
答案: D。排序加树状数组统计逆序对。
21. 对于输入 4 1 3 4 2 1 3 4 2,输出为
答案: B。代入程序统计得到逆序对数为 2。
(二)阅读下面的程序代码,回答 22~27 题
代码解读: 这段程序在求满足条件的整数 x 的个数,本质条件可以整理成 gcd(a0, x) = a1 且 lcm(x, b0) = b1。因为合法的 x 必须是 b1 的约数,所以程序通过枚举 b1 的约数对 (x, b1/x),再结合两次 gcd 判断筛掉不合法情况。整体思路属于“数论条件变形 + 约数枚举”。
01 #include <cstdio>
02 using namespace std;
03 int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
04 int main() {
05 int T;
06 scanf("%d", &T);
07 while (T--) {
08 int a0, a1, b0, b1;
09 scanf("%d%d%d%d", &a0, &a1, &b0, &b1);
10 int p = a0 / a1, q = b1 / b0, ans = 0;
11 for (int x = 1; x * x <= b1; x++) {
12 if (b1 % x == 0) {
13 if (x % a1 == 0 && gcd(x / a1, p) == 1 && gcd(q, b1 / x) == 1) ans++;
14 int y = b1 / x;
15 if (x == y) continue;
16 if (y % a1 == 0 && gcd(y / a1, p) == 1 && gcd(q, b1 / y) == 1) ans++;
17 }
18 }
19 printf("%d\n", ans);
20 }
21 return 0;
22 }
22. 将第 15 行 continue 改成 break 不影响结果。
答案: A。此时 x==y 已到平方根处,后续也不会再有新约数对。
23. 若输入是 1 4 1 96 288,则输出是 5。
答案: B。实际输出不是 5。
24. 第 11 行从 x=a1 开始枚举一定更快。
答案: B。并非“一定”,当 a1=1 时没有提升。
25. 本题有时间复杂度更优的做法。
答案: A。可改为基于素因数分解和约数枚举。
26. 本题实质上是求满足哪个条件的 x 的个数?
A. gcd(a0,x)=a1 且 lcm(x,b1)=b0
B. lcm(a1,x)=a0 且 lcm(x,b0)=b1
C. gcd(a0,x)=a1 且 gcd(x,b1)=b0
D. gcd(a0,x)=a1 且 lcm(x,b0)=b1
答案: D。由条件变形即可得到。
27. 若输入是 1 95 1 37 1776,则输出为
答案: B。满足条件的共有两个数。
三、完善程序(共 5 题)
给定整数 a,b,完成程序
代码解读: 这道完善程序题的核心思想是“通过 ±1 和 /2 操作,把两个数逐步压缩到接近的位置,再统计最少操作次数”。程序先分别处理 a > b 和 b >= 2a 的情况:如果当前数是奇数,就先加一或减一把它变成偶数;如果是偶数,就直接除以 2。这样做的本质是贪心地消去二进制最低位,使数值更快缩小。后半段则继续比较两个已经接近的数,补齐最后几步操作,因此整题考查的是“贪心 + 二进制视角下的状态缩减”。
01 #include <bits/stdc++.h>
02 #define int long long
03 using namespace std;
04 signed main() {
05 int a, b;
06 scanf("%lld%lld", &a, &b);
07 int cnt = 0;
08 while (a > b) {
09 cnt++;
10 if (a % 2 == 1) ①;
11 else a /= 2;
12 }
13 while (b >= a * 2) {
14 cnt++;
15 if (b % 2 == 1) ②;
16 else b /= 2;
17 }
18 if (③) { ... }
19 ...
20 }
28. ①处应填
A. a++
B. a--
C. a*=2
D. a/=2
答案: A。奇数时先加一再除二才有意义。
29. ②处应填
A. b++
B. b--
C. b*=2
D. b/=2
答案: B。这里需要把较大的奇数 b 往下调。
30. ③处应填
A. a = b
B. a == b - 1
C. a = b - 1
D. a == b
答案: D。若已相等,可直接输出。
31. ④处应填
A. aa--, bs--
B. aa--, bs++
C. aa++, bs--
D. aa++, bs++
答案: D。与后续统一除二配合时,奇数需先补成偶数。
32. ⑤处应填
A. bb--, bs--
B. bb++, bs++
C. bb--, bs++
D. bb++, bs--
答案: C。为了后续除二,需要把奇数 bb 先减一并计次。