GESP 客观题评测系统

试卷解析 · 理解每一步

2022 CCF CSP-S 第一轮(提高级 C++)

CSPS-2022-R1

试卷解析总览,可直接查看每题答案与解析。

一、单项选择题

第 1 题(单选题,2 分)

在 Linux 系统终端中,用于切换工作目录的命令为()。

A.
ls
B.
cd
C.
cp
D.
all

正确答案B

解析详情
答案 BLinux 目录操作命令
思路与推导
  1. cd 表示 change directory,执行 cd work 后,当前工作目录切换到 work,后续相对路径也从新目录解释。

  2. ls 只列出内容,cp 用于复制;要改变当前工作目录应选 B。

第 2 题(单选题,2 分)

你同时用 time 命令和秒表为某个程序在单核 CPU 的运行计时。假如 time 命令的输出如下:

real 0m30.721s

user 0m24.579s

sys 0m6.123s

以下最接近秒表计时的时长为()。

A.
30s
B.
24s
C.
18s
D.
6s

正确答案A

解析详情
答案 ALinux time 命令
思路与推导
  1. 秒表从程序启动计时到程序结束,测的是经过的实际时间,对应 real=30.721 秒,最接近 30 秒,选 A。

  2. user=24.579 秒是用户态 CPU 时间,sys=6.123 秒是内核态 CPU 时间;它们不包含所有等待时间,不能用其中一项代替秒表读数。

第 3 题(单选题,2 分)

若元素 a、b、c、d、e、f 依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次退栈操作,则不可能得到的出栈序列是()。

A.
dcebfa
B.
cbdaef
C.
bcaefd
D.
afedcb

正确答案D

解析详情
答案 D栈的出栈序列
思路与推导
  1. 若要输出序列 afedcb,先把 a 入栈并立即弹出;随后要让 f 成为栈顶,必须依次压入 b、c、d、e、f。

  2. 此时待输入元素已耗尽,要依次输出 f,e,d,c,b,只能连续出栈 5 次,中间再无新元素可入栈打断,违反不能连续三次退栈。

  3. 因此 D 不可能;这并非普通栈序本身非法,而是额外操作限制排除了它。

第 4 题(单选题,2 分)

考虑对 n 个数进行排序,以下最坏时间复杂度低于 O(n2)O(n^{2}) 的排序方法是()。

A.
插入排序
B.
冒泡排序
C.
归并排序
D.
快速排序

正确答案C

解析详情
答案 C排序算法最坏时间复杂度
思路与推导
  1. 归并排序把数据反复二分,共有约 log⁡2n\log_2 n 层;同一层的合并区间互不重叠,处理总量为 n。

  2. 所以最坏时间为 O(nlog⁡n)O(n\log n),比平方级低,选 C。插入、冒泡以及未加保护的快速排序,都存在 Θ(n2)\Theta(n^2) 的输入。

第 5 题(单选题,2 分)

假设在基数排序过程中,受宇宙射线的影响,某项数据异变为一个完全不同的值。请问排序算法结束后,可能出现的最坏情况是()。

A.
移除受影响的数据后,最终序列是有序序列
B.
移除受影响的数据后,最终序列是前后两个有序的子序列
C.
移除受影响的数据后,最终序列是一个有序的子序列和一个基本无序的子序列
D.
移除受影响的数据后,最终序列基本无序

正确答案A

解析详情
答案 A基数排序的稳定性
思路与推导
  1. 基数排序的每一趟按当前位进行稳定分配:若两个未受影响元素的当前位相同,仍保持上一趟的相对次序。

  2. 异常元素即使改变数值和所处桶,也不会改变其他两个正常元素的数位比较结果。忽略它之后,各趟对正常元素仍组成同样的稳定排序过程。

  3. 因此移除这一个异常元素后,其余数据仍有序,最坏也只会有它自身的位置不正确,选 A。

第 6 题(单选题,2 分)

计算机系统用小端(Little Endian)和大端(Big Endian)来描述多字节数据的存储地址顺序模式,其中小端表示将低位字节数据存储在低地址的模式、大端表示将高位字节数据存储在低地址的模式。在小端模式的系统和大端模式的系统分别编译和运行以下 C++代码段表示的程序,将分别输出什么结果?( )

unsigned x = 0xDEADBEEF;

unsigned char *p = (unsigned char *)&x;

printf("%X", *p);
A.
EF、EF
B.
EF、DE
C.
DE、EF
D.
DE、DE

正确答案B

解析详情
答案 B大小端存储
思路与推导
  1. 将 0xDEADBEEF 按字节分为 DE、AD、BE、EF。p 指向变量首地址,即最低内存地址,并且只读取一个字节。

  2. 小端的地址从低到高存 EF、BE、AD、DE,故读到 EF;大端存 DE、AD、BE、EF,故读到 DE。

  3. %X 用十六进制输出该字节,因此分别为 EF、DE,选 B。

第 7 题(单选题,2 分)

一个深度为5(根结点深度为1)的完全3叉树,按前序遍历的顺序给结点从1开始编号,

则第 100 号结点的父结点是第()号。

A.
95
B.
96
C.
97
D.
98

正确答案C

解析详情
答案 C多叉树前序遍历编号
思路与推导
  1. 按完整各层的三叉树模型,高为 4 的子树含 1+3+9+27=401+3+9+27=40 个结点。根编号 1,三棵子树的前序编号段分别为 2…41、42…81、82…121。

  2. 100 落在以 82 为根的第三棵子树,其中每个孩子子树有 13 个结点,编号段为 83…95、96…108、109…121。

  3. 再进入根 96 的子树,其第一个孩子编号 97,紧随其后的三个叶子为 98、99、100。故父结点为 97,选 C。

  4. 这里沿用答案把“完全3叉树”理解为各层全满的模型;若按末层可不满、从左向右填充的通常完全树定义,仅给深度无法唯一确定。例如 101 个结点的完全三叉树,前序第 100 个结点的父编号为 98。应区分原题的术语口径与一般定义。

第 8 题(单选题,2 分)

强连通图的性质不包括():

A.
每个顶点的度数至少为 1
B.
任意两个顶点之间都有边相连
C.
任意两个顶点之间都有路径相连
D.
每个顶点至少都连有一条边

正确答案B

解析详情
答案 B有向图强连通
思路与推导
  1. 强连通要求任意两点 u、v 都有 u 到 v 以及 v 到 u 的有向路径,不要求路径长度为 1。

  2. 例如有向环 1→2→3→4→1 是强连通图,但 1 和 3 之间没有直接边。它足以否定 B。

  3. 其余关于相邻边的描述按本题通常的非单点图口径理解,核心区别是“路径”与“直接边”。

第 9 题(单选题,2 分)

每个顶点度数均为2的无向图称为“2正规图”。由编号为从1到n的顶点构成的所有2正规图,其中包含欧拉回路的不同2正规图的数量为()。

A.
n!
B.
(n−1)!(n-1)!
C.
n! / 2
D.
(n−1)!(n-1)!/2

正确答案D

解析详情
答案 D欧拉回路与圆排列
思路与推导
  1. 每点度数为 2 的简单无向图由若干个环组成。若要有遍历所有边的欧拉回路,这些环不能分属不同连通分量,所以必须是经过全部 n 点的一个环。

  2. 固定顶点 1 作为书写起点,剩余顶点有 (n−1)!(n-1)! 种排列;把顺时针与逆时针视作同一个无向环,再除以 2。

  3. 因此在通常 n≥3 的环模型下,方案数为 (n−1)!/2(n-1)!/2,选 D。

第 10 题(单选题,2 分)

共有 8 人选修了程序设计课程,期末大作业要求由 2 人组成的团队完成。假设不区分每个团队内 2 人的角色和作用,请问共有多少种可能的组队方案。()

A.
28
B.
32
C.
56
D.
64

正确答案A

解析详情
答案 A组合计数
思路与推导
  1. 按选项所考查的“一支两人团队”计数,从 8 人中先选第一人有 8 种,再选另一人有 7 种。

  2. 同一对成员交换选择顺序仍是同一支队伍,故除以 2,得到 (82)=28\binom{8}{2}=28,选 A。

  3. 若问题改成把全部 8 人同时分成 4 支无标号队伍,则会是 105 种;那是不同的计数任务。

第 11 题(单选题,2 分)

小明希望选到形如“省 A·LLDDD”的车牌号。车牌号在“·”之前的内容固定不变;后面的 5 位号码中,前 2 位必须是大写英文字母,后 3 位必须是阿拉伯数字(ℱ代表 A 至 Z,ℱ表示 0 至 9,两个ℱ和三个ℱ之间可能相同也可能不同)。请问总共有多少个可供选择的车牌

号。()

A.
20280
B.
52000
C.
676000
D.
1757600

正确答案C

解析详情
答案 C乘法原理
思路与推导
  1. 固定的“省 A·”不产生选择。后面两位字母各可选 A 至 Z 共 26 种,三位数字各可选 0 至 9 共 10 种。

  2. 不同位置独立且允许重复,由乘法原理得到 262×103=67600026^2\times10^3=676000,选 C。数字部分是编号,不受三位整数首位不能为 0 的限制。

第 12 题(单选题,2 分)

给定地址区间为 0~9 的哈希表,哈希函数为 h(x)=x%10h(x) = x \% 10,采用线性探查的冲突解决策略(对于出现冲突情况,会往后探查第一个空的地址存储;若地址 9 冲突了则从地址 0 重新开始探查)。哈希表初始为空表,依次存储(71, 23, 73, 99, 44, 79, 89)后,请问 89 存储在哈希表哪个地址中。()

A.
9
B.
0
C.
1
D.
2

正确答案D

解析详情
答案 D哈希表线性探查
思路与推导
  1. 逐项插入:71→1,23→3;73 初始位置 3 已占,顺延到 4;99→9;44 的位置 4 已占,顺延到 5;79 从 9 回绕到空位 0。

  2. 最后插入 89 时,从余数位置 9 开始,9、0、1 都已占用,位置 2 为空,因此落在 2,选 D。

图解 · 看懂这一步线性探测的实际落点

左右滑动表格,可查看完整内容。

插入值初始地址探测经过最终地址
71111
23333
7333 → 44
99999
4444 → 55
7999 → 00
8999 → 0 → 1 → 22

地址冲突后逐格后移,从 9 继续时回绕到 0。

第 13 题(单选题,2 分)

对于给定的 n,分析以下代码段对应的时间复杂度,其中最为准确的时间复杂度为()。

13. 对于给定的 n,分析以下代码段对应的时间复杂度,其中最为准确的时间复杂度为()。

int i, j, k = 0;

for (i = 0; i < n; i++) {

    for (j = 0; j < n; j *= 2) {

        k = k + n / 2;

    }

}
A.
O(n)O(n)
B.
O(nlog⁡n)O(n \log n)
C.
O(nn)O(n\sqrt{n})
D.
O(n2)O(n^{2})

正确答案B

解析详情
答案 B嵌套循环时间复杂度
思路与推导
  1. 原 PDF 的内层更新语句是 j*=2,但初值仍写为 j=0。n>0 时,0 每次乘 2 仍为 0,j<n 永远成立;该原卷代码不能作为一个正常结束的排序或计数循环分析。

  2. 给定答案 B 对应的是把初值改为 1 后的命题意图:内层依次取 1,2,4,…,执行 O(log⁡n)O(\log n) 次,外层 n 次,得到 O(nlog⁡n)O(n\log n)。

  3. 本题保留原卷及其答案,同时明确区分:B 是勘误后的复杂度,不是原样程序的时间复杂度;也不能用一次运行假定它会在整数溢出后正常结束。

第 14 题(单选题,2 分)

以比较为基本运算,在 n 个数的数组中找最大的数,在最坏情况下至少要做()次运算。

A.
n/2
B.
n-1
C.
n
D.
n+1n+1

正确答案B

解析详情
答案 B比较模型下求最大值
思路与推导
  1. 每个不是最大值的元素,至少要在一次比较中输给别的元素,才能确定排除它。共有 n−1 个这样的元素,而一次比较最多新增一个被排除者。

  2. 所以至少要比较 n−1 次;从第一个元素作候选,依次与其他元素比较,恰能达到该下界。因此选 B。

第 15 题(单选题,2 分)

ack 函数在输入参数“ (2,2)(2,2)”时的返回值为()。

unsigned ack(unsigned m, unsigned n) {
    if (m == 0) return n + 1;
    if (n == 0) return ack(m - 1, 1);
    return ack(m - 1, ack(m, n - 1));
}
A.
5
B.
7
C.
9
D.
13

正确答案B

解析详情
答案 BAckermann 递归
思路与推导
  1. 当 m=1 时,ack(1,0)=ack(0,1)=2;每增加一次 n,调用 ack(0,…) 把结果加 1,所以 ack(1,n)=n+2。

  2. 于是 ack(2,0)=ack(1,1)=3,ack(2,1)=ack(1,3)=5,ack(2,2)=ack(1,5)=7。

  3. 因此选 B;第三个分支必须先算内层,再把结果作为外层第二个参数。

二、阅读程序(1)

 1 | #include <iostream>
 2 | #include <string>
 3 | #include <vector>
 4 | 
 5 | using namespace std;
 6 | 
 7 | int f(const string &s, const string &t)
 8 | {
 9 |     int n = s.length(), m = t.length();
10 | 
11 |     vector<int> shift(128, m + 1);
12 | 
13 |     int i, j;
14 | 
15 |     for (j = 0; j < m; j++)
16 |         shift[t[j]] = m - j;
17 | 
18 |     for (i = 0; i <= n - m; i += shift[s[i + m]]) {
19 |         j = 0;
20 |         while (j < m && s[i + j] == t[j]) j++;
21 |         if (j == m) return i;
22 |     }
23 | 
24 |     return -1;
25 | }
26 | 
27 | int main()
28 | {
29 |     string a, b;
30 |     cin >> a >> b;
31 |     cout << f(a, b) << endl;
32 |     return 0;
33 | }

假设输入字符串由 ASCII 可见字符组成,完成下面的判断题和单选题:

第 16 题(判断题,1 分)

当输入为“abcde fg”时,输出为-1。

正确答案正确

解析详情
答案 正确字符串模式匹配
思路与推导
  1. 输入运算符把空格两侧分别读入 s=abcde、t=fg,模式长度 m=2。shift 中 f 为 2、g 为 1,其他字符默认 3。

  2. 候选起点 i=0 时首字符失配,窗口后字符 c 的位移为 3,于是跳到 i=3;这里 d 与 f 仍不等,再跳出合法起点范围。

  3. 没有完整匹配,返回 −1,判断正确。

第 17 题(判断题,1.5 分)

当输入为 “abbababbbab abab” 时,输出为 4。()

正确答案错误

解析详情
答案 错误字符串匹配与下标
思路与推导
  1. 主串 abbababbbab 的零基下标 3、4、5、6 依次为 a、b、a、b,组成第一次出现的 abab。

  2. 程序返回起点下标 i,而不是从 1 开始数的字符位置,因此输出 3。题目说输出 4,判断错误。

第 18 题(判断题,1.5 分)

当输入为 “GoodLuckCsp2022 22” 时,第 20 行的 “j++” 语句执行次数为 2。

正确答案正确

解析详情
答案 正确坏字符跳转字符串匹配
思路与推导
  1. 模式“22”长度为 2,字符 2 最后一次出现于下标 1,因此 shift[2]=1;其他字符的默认位移为 3。

  2. 实际候选起点依次为 0、3、6、9、12。前四次首字符就失配,不执行 j++;i=12 时末尾两个 2 都匹配,使 j 从 0 增至 2。

  3. 因此 j++ 总计 2 次,判断正确。

第 19 题(单选题,3 分)

该算法最坏情况下的时间复杂度为()。

A.
O(n+m)O(n+m)
B.
O(nlog⁡m)O(n\log m)
C.
O(mlog⁡n)O(m\log n)
D.
O(nm)O(nm)

正确答案D

解析详情
答案 D字符串匹配最坏复杂度
思路与推导
  1. 每次尝试匹配,while 最多成功或检查约 m 个字符。跳转表虽能跳过一些起点,但最坏仍有与 n 同阶的候选窗口。

  2. 例如主串全为 a,模式为若干个 a 后接 b,每次先比较长 a 前缀再失败,窗口后字符 a 只让起点移动 2 位。

  3. 当 n 远大于 m 时,需要 Θ(n)\Theta(n) 个窗口,每个 Θ(m)\Theta(m) 次工作,最坏为 O(nm)O(nm),选 D。

第 20 题(单选题,3 分)

f(a, b)与下列()语句的功能最类似。

A.
a.find(b)
B.
a.rfind(b)
C.
a.substr(b)
D.
a.compare(b)

正确答案A

解析详情
答案 A标准字符串查找
思路与推导
  1. 函数依次检查可能的匹配起点,首次完整匹配就 return i;若全部失败,则返回 −1。

  2. a.find(b) 同样返回 b 在 a 中第一次出现的位置,因此功能最接近 A。严格说 find 的未找到标记是 string::npos,而此函数用 int 的 −1;题目比较的是查找功能。

第 21 题(单选题,3 分)

当输入为“baaabaaabaaabaaaa aaaa”,第20行的“j++”语句执行次数为()。

A.
9
B.
10
C.
11
D.
12

正确答案B

解析详情
答案 B字符串匹配过程模拟
思路与推导
  1. 模式 aaaa 的 shift[a]=1;其他字符默认跳 5。程序只尝试起点 0、5、6、7、8、13,不会逐个起点检查。

  2. 这些窗口分别成功匹配 0、3、2、1、0、4 个字符;每成功一个字符才执行一次 j++,失败的那次比较不计。

  3. 合计 0+3+2+1+0+4=100+3+2+1+0+4=10,选 B。

图解 · 看懂这一步Sunday 匹配中 j++ 的次数

左右滑动表格,可查看完整内容。

起点 i匹配成功字符数累计次数
000
533
625
716
806
13410

起点为零基下标,只记录实际访问的窗口;成功匹配一个字符才执行一次 j++。

二、阅读程序(2)

 1 | #include <iostream>
 2 |
 3 | using namespace std;
 4 |
 5 | const int MAXN = 105;
 6 |
 7 | int n, m, k, val[MAXN];
 8 | int temp[MAXN], cnt[MAXN];
 9 |
10 | void init()
11 | {
12 |     cin >> n >> k;
13 |     for (int i = 0; i < n; i++) cin >> val[i];
14 |     int maximum = val[0];
15 |     for (int i = 1; i < n; i++)
16 |         if (val[i] > maximum) maximum = val[i];
17 |     m = 1;
18 |     while (maximum >= k) {
19 |         maximum /= k;
20 |         m++;
21 |     }
22 | }
23 |
24 | void solve()
25 | {
26 |     int base = 1;
27 |     for (int i = 0; i < m; i++) {
28 |         for (int j = 0; j < k; j++) cnt[j] = 0;
29 |         for (int j = 0; j < n; j++) cnt[val[j] / base % k]++;
30 |         for (int j = 1; j < k; j++) cnt[j] += cnt[j - 1];
31 |         for (int j = n - 1; j >= 0; j--) {
32 |             temp[cnt[val[j] / base % k] - 1] = val[j];
33 |             cnt[val[j] / base % k]--;
34 |         }
35 |         for (int j = 0; j < n; j++) val[j] = temp[j];
36 |         base *= k;
37 |     }
38 | }
39 |
40 | int main()
41 | {
42 |     init();
43 |     solve();
44 |     for (int i = 0; i < n; i++) cout << val[i] << ' ';
45 |     cout << endl;
46 |     return 0;
47 | }

假设输入的 n 为不大于 100 的正整数,k 为不小于 2 且不大于 100 的正整数,val[i] 在 int 表示范围内,完成下面的判断题和单选题:

第 22 题(判断题,1.5 分)

这是一个不稳定的排序算法。()

正确答案错误

解析详情
答案 错误稳定的计数排序与基数排序
思路与推导
  1. 每一趟利用计数前缀和确定数位相同元素的区间,再从原数组末尾向前放置,放一个就把该桶的末位置减一。

  2. 若同位的两个元素 u 原本在 v 前面,则 v 先放到较后位置,u 后放到较前位置,相对顺序保持不变。

  3. 所以单趟计数排序稳定,各趟组成的基数排序也稳定,“不稳定”这一断言错误。此处按算法所需的非负键值与不溢出前提分析。

第 23 题(判断题,1.5 分)

该算法的空间复杂度仅与 n 有关。()

正确答案错误

解析详情
答案 错误基数排序空间复杂度
思路与推导
  1. 一趟排序至少需要 n 个位置保存 temp,此外还要有 k 个桶来统计每个数位出现次数。

  2. 按随输入规模分配空间的算法模型,额外空间为 O(n+k)O(n+k),会受到基数 k 的影响,所以断言错误。原代码用固定 MAXN 数组实现,不应把固定容量写法当成可扩展算法的空间结论。

第 24 题(判断题,1.5 分)

该算法的时间复杂度为 O(m(n+k))O(m(n + k))。()

正确答案正确

解析详情
答案 正确基数排序时间复杂度
思路与推导
  1. 一趟包括清空 k 个桶、统计 n 个元素、累加 k 个桶的前缀和、回填 n 个元素及复制 n 个元素,总量为 O(n+k)O(n+k)。

  2. 这样的过程执行 m 次,对应最大数的 k 进制位数,因此排序阶段为 O(m(n+k))O(m(n+k)),判断正确;这里采用桶下标有效且整数计算不溢出的正常排序前提。

第 25 题(单选题,3 分)

当输入为 “5 3 98 26 91 37 46” 时,程序第一次执行到第 36 行,val[] 数组的内容依次为()。

A.
91 26 46 37 98
B.
91 46 37 26 98
C.
98 26 46 91 37
D.
91 37 46 98 26

正确答案D

解析详情
答案 D基数排序单趟模拟
思路与推导
  1. 第一趟 base=1,数位取 val[j]%3。98、26 的余数为 2,91、37、46 的余数为 1,没有余数 0 的数。

  2. 稳定计数排序把余数 1 的组先放,组内保持 91,37,46;余数 2 的组保持 98,26。

  3. 第一次执行到第 36 行时已经复制回 val,得到 91 37 46 98 26,选 D。

图解 · 看懂这一步第一趟三进制稳定分桶

左右滑动表格,可查看完整内容。

桶编号原顺序进入桶的元素回填位置(零基)
0无无
191、37、460、1、2
298、263、4

按个位分桶;相同余数的元素保留输入顺序。

第 26 题(单选题,3 分)

若 val[i] 的最大值为 100,k 取()时算法运算次数最少。

A.
2
B.
3
C.
10
D.
不确定

正确答案C

解析详情
答案 C基数选择与排序轮数
思路与推导
  1. 给定答案 C 对应比较趟数:最大值 100 分别需要 7 位二进制、5 位三进制、3 位十进制,因此 k=10 的趟数最少。

  2. 但题干问完整“运算次数”,每趟还要处理 k 个桶,不能只数趟数。按代码各循环体执行次数粗计,每趟为 k+n+(k−1)+n+n=3n+2k−1。

  3. 例如 n=1 时,k=2、3、10 分别得到 42、40、66 次这一口径下的循环体执行量,10 并非最少。因此缺少 n 与具体成本口径时,不能严格证明 C 为唯一最优;此处保留原答案并说明其简化假设。

第 27 题(单选题,3 分)

当输入的 k 比 val[i] 的最大值还大时,该算法退化为()算法。

A.
选择排序
B.
冒泡排序
C.
计数排序
D.
桶排序

正确答案C

解析详情
答案 C计数排序
思路与推导
  1. 当所有非负键值都小于 k 时,init 中 maximum>=k 一开始为假,所以 m=1。

  2. 唯一的一趟中 base=1,val[j]/base%k 等于 val[j] 本身,计数桶直接按完整数值统计,再按前缀和回填。

  3. 这正是计数排序,选 C,无需再处理高位。

二、阅读程序(3)

 1 | #include <iostream>
 2 | #include <algorithm>
 3 |
 4 | using namespace std;
 5 |
 6 | const int MAXL = 1000;
 7 |
 8 | int n, k, ans[MAXL];
 9 |
10 | int main(void)
11 | {
12 |     cin >> n >> k;
13 |     if (!n) cout << 0 << endl;
14 |     else
15 |     {
16 |         int m = 0;
17 |         while (n)
18 |         {
19 |             ans[m++] = (n % (-k) + k) % k;
20 |             n = (ans[m - 1] - n) / k;
21 |         }
22 |         for (int i = m - 1; i >= 0; i--)
23 |             cout << char(ans[i] >= 10 ?
24 |                 ans[i] + 'A' - 10 :
25 |                 ans[i] + '0');
26 |         cout << endl;
27 |     }
28 |     return 0;
29 | }

假设输入的 n 在 int 范围内,k 为不小于 2 且不大于 36 的正整数,完成下面的判断题和单选题:

第 28 题(判断题,1.5 分)

该算法的时间复杂度为 O(log⁡kn)O(\log_k n)。 ()

正确答案正确

解析详情
答案 正确负进制转换复杂度
思路与推导
  1. 每轮选取余数 r∈[0,k−1],并更新商 q=(r−n)/kq=(r-n)/k,保持 n=(−k)q+rn=(-k)q+r。

  2. 于是商的绝对值满足 ∣q∣≤∣n∣/k+1|q|\le |n|/k+1,经过与对数同阶的次数就降至常数范围,算法生成一位后再处理余下商。

  3. 按非零 n 的通常规模写法,时间为 O(log⁡k∣n∣+1)O(\log_k|n|+1),题目意指对数量级,判断正确;负数不能直接写实数对数 log⁡kn\log_k n。

第 29 题(判断题,1.5 分)

删除第 23 行的强制类型转换,程序的行为不变。 ()

正确答案错误

解析详情
答案 错误C++ 输出类型与条件运算符
思路与推导
  1. 即使加上字符常量,ans[i]+'0' 和 ans[i]+'A'−10 的算术结果也是整数。char(...) 把它转回字符,使 cout 输出字形。

  2. 例如输入 1 2 时,有转换输出字符“1”;删除转换则输出字符编码的十进制值 49。因此程序行为改变,判断错误。

第 30 题(判断题,1.5 分)

除非输入的 n 为 0,否则程序输出的字符数为 O(∣log⁡k∣n∣+1)O(|\log_k |n| + 1)。 ()

正确答案正确

解析详情
答案 正确负进制表示长度
思路与推导
  1. 每轮写入一个负 k 进制数位,最终 m 就是位数,倒序循环每次输出一个字符。

  2. 商的绝对值大致每轮缩小 k 倍,所以位数为 O(log⁡k∣n∣+1)O(\log_k|n|+1),判断正确。

  3. 末尾的加 1 覆盖 n=1 等对数为 0 的小规模情形;n=0 则由单独分支输出一个 0。

第 31 题(单选题,3 分)

当输入为 “100 7” 时,输出为()。

A.
202
B.
1515
C.
244
D.
1754

正确答案A

解析详情
答案 A负进制转换
思路与推导
  1. 当前 n=100 时,规范余数为 2,新的 n=(2−100)/7=−14。

  2. n=−14 时余数为 0,新的 n=2;再取余数 2,新的 n=0,结束。

  3. 得到低位到高位 [2,0,2],倒序输出 202,且 2(−7)2+2=1002(-7)^2+2=100,选 A。

第 32 题(单选题,3 分)

当输入为 “-255 8” 时,输出为 “( )”。

A.
1400
B.
1401
C.
417
D.
400

正确答案B

解析详情
答案 B负进制转换
思路与推导
  1. 逐轮规范余数与新商为:−255→余数 1、商 32;32→余数 0、商 −4;−4→余数 4、商 1;1→余数 1、商 0。

  2. 低位到高位为 [1,0,4,1],倒序得到 1401。

  3. 验证 1(−8)3+4(−8)2+1=−512+256+1=−2551(-8)^3+4(-8)^2+1=-512+256+1=-255,选 B。

第 33 题(单选题,3 分)

当输入为 “1000000 19” 时,输出为 “( )。

A.
BG939
B.
87GIB
C.
1CD428
D.
7CF1B

正确答案B

解析详情
答案 B负进制转换与高位数字编码
思路与推导
  1. 每轮的“当前 n→数位、新商”为:1000000→11,−52631;−52631→18,2771;2771→16,−145;−145→7,8;8→8,0。

  2. 数位 11、18、16 分别编码为 B、I、G,低位到高位为 B,I,G,7,8,倒序输出 87GIB,选 B。

  3. 代回负十九进制位权,8×194−7×193+16×192−18×19+11=10000008\times19^4-7\times19^3+16\times19^2-18\times19+11=1000000,与输入一致。

图解 · 看懂这一步1000000 转为负十九进制

左右滑动表格,可查看完整内容。

轮次当前数规范余数新商字符
1100000011−52631B
2−52631182771I
3277116−145G
4−145787
58808

每行保持 当前数 = (−19) × 新商 + 余数;将余数字符倒序得到 87GIB。

三、完善程序(1)

(1)(归并第 k 小)已知两个长度均为 n 的有序数组 a1 和 a2(均为递增序,但不保证严格单调递增),并且给定正整数 k( 1⩽k⩽2n1 \leqslant k \leqslant 2n),求数组 a1 和 a2 归并排序后的数组里第 k 小的数值。

试补全程序。

#include <bits/stdc++.h>
using namespace std;

int solve(int *a1, int *a2, int n, int k) {
int left1 = 0, right1 = n - 1;
int left2 = 0, right2 = n - 1;
while (left1 <= right1 && left2 <= right2) {

int m1 = (left1 + right1) >> 1;
int m2 = (left2 + right2) >> 1;
int cnt = ①;
if (②) {
    if (cnt < k) left1 = m1 + 1;
    else right2 = m2 - 1;
} else {
    if (cnt < k) left2 = m2 + 1;
    else right1 = m1 - 1;
}
}

if (③) {
    if (left1 == 0) {
        return a2[k - 1];
    } else {
        int x = a1[left1 - 1], ④;
        return std::max(x, y);
    }
} else {
    if (left2 == 0) {
        return a1[k - 1];
    } else {
        int x = a2[left2 - 1], ⑤;
        return std::max(x, y);
    }
}
}

第 34 题(单选题,3 分)

①处应填( )

A.
(m1 + m2) * 2
B.
(m1 - 1) + (m2 - 1)
C.
m1 + m2
D.
(m1 + 1) + (m2 + 1)

正确答案C

解析详情
答案 C两个有序数组的第 k 小
思路与推导
  1. 若 a1[m1]≤a2[m2],严格小于 a1[m1] 的元素至多来自第一数组前 m1 项和第二数组前 m2 项,总数至多为 m1+m2。若这个和小于 k,则 a1[m1] 不会大于合并后的第 k 小值,可以把第一侧这个中点及以前的元素纳入左前缀。

  2. 反之,若 m1+m2≥k,则 a2[m2] 前面已有足够多的不大于它的候选,可以缩小第二侧右边界。另一分支完全对称,因此①采用零基下标和 m1+m2,选 C。这里比较的是分割位置,不应直接替换成两个前缀长度之和。

  3. 需同时注意原卷的边界缺陷:例如 n=1、k=1、a1=[1]、a2=[2],循环后 left1=1,收尾访问 a2[k−left1−1]=a2[−1]。本题答案体现原卷分割意图,不能据此断言代码对所有输入都安全。

第 35 题(单选题,3 分)

②处应填( )

A.
a1[m1] == a2[m2]
B.
a1[m1] <= a2[m2]
C.
a1[m1] >= a2[m2]
D.
a1[m1] != a2[m2]

正确答案B

解析详情
答案 B二分消去
思路与推导
  1. ②为真时,cnt<k 会向右移动 left1;否则向左移动 right2。因此这一分支应对应 a1 的中点不大于 a2 的中点。

  2. 填 a1[m1]<=a2[m2] 后,两支恰好按较小/较大中点作对称处理,选 B;只比较相等或不等不能确定该移动哪一侧。

  3. 整体代码的空前缀访问风险见第 34、37 题,局部比较方向正确并不能消除它。

第 36 题(单选题,3 分)

③处应填( )

A.
left1 == right1
B.
left1 < right1
C.
left1 > right1
D.
left1 != right1

正确答案C

解析详情
答案 C二分边界终止条件
思路与推导
  1. 循环前提是两边候选区间都非空。对包含端点的闭区间 [left1,right1],只有 left1>right1 才表示它已经为空。

  2. 因此③选 C,用来区分哪侧搜索先结束。left1==right1 时还剩一个候选元素,不应判为空。

第 37 题(单选题,3 分)

④处应填( )

A.
y = a1[k - left2 - 1]
B.
y = a1[k - left2]
C.
y = a2[k - left1 - 1]
D.
y = a2[k - left1]

正确答案C

解析详情
答案 C有序数组分割
思路与推导
  1. 按分割的计数意图,第一侧前缀已有 left1 个元素,第二侧应补 k−left1 个。第二侧最后一个元素的零基下标为 k−left1−1,所以④选 C。

  2. 若两侧前缀都非空,取 a1[left1−1] 与 a2[k−left1−1] 的较大值,就是合并前缀的末项。

  3. 但是 k−left1=0 时第二侧为空,这个下标为 −1;原代码只检查 left1==0,没有检查这一对称边界。因此该表达式有边界前提,不能直接作为可复用的完整第 k 小模板。

第 38 题(单选题,3 分)

⑤处应填( )

A.
y = a1[k - left2 - 1]
B.
y = a1[k - left2]
C.
y = a2[k - left1 - 1]
D.
y = a2[k - left1]

正确答案A

解析详情
答案 A有序数组分割的对称处理
思路与推导
  1. 这一分支与上一分支对称:第二侧前缀有 left2 个元素,第一侧需要 k−left2 个,对应末元素下标 k−left2−1。

  2. 因此⑤填 y=a1[k−left2−1],选 A,再与 a2[left2−1] 取最大值。

  3. 同样必须警惕 k−left2=0 时的空前缀;原卷未提供该边界保护,解析说明的是选项的分割含义。

三、完善程序(2)

(2)(容器分水)有两个容器,容器 1 的容量为为 a 升,容器 2 的容量为 b 升;同时允许下列的三种操作,分别为:

1)FILL(i):用水龙头将容器 i(i∈{1,2})灌满水;

2)DROP(i):将容器 i 的水倒进下水道;

3)POUR(i,j):将容器 i 的水倒进容器 j(完成此操作后,要么容器 j 被灌满,要么容器 i 被清空)。

求只使用上述的两个容器和三种操作,获得恰好 c 升水的最少操作数和操作序列。上述 a、b、c 均为不超过 100 的正整数,且 c≤max⁡{a,b}c \leq \max\{a, b\}。

试补全程序。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;

int f[N][N];
int ans;
int a, b, c;
int init;

int dfs(int x, int y) {
    if (f[x][y] != init)
        return f[x][y];
    if (x == c || y == c)
        return f[x][y] = 0;
    f[x][y] = init - 1;
    f[x][y] = min(f[x][y], dfs(a, y) + 1);
    f[x][y] = min(f[x][y], dfs(x, b) + 1);
    f[x][y] = min(f[x][y], dfs(0, y) + 1);
    f[x][y] = min(f[x][y], dfs(x, 0) + 1);
    int t = min(a - x, y);
    f[x][y] = min(f[x][y], ①);
    t = min(x, b - y);
    f[x][y] = min(f[x][y], ②);
    return f[x][y];
}

void go(int x, int y) {
    if (③)
        return;
    if (f[x][y] == dfs(a, y) + 1) {
        cout << "FILL(1)" << endl;
        go(a, y);
    } else if (f[x][y] == dfs(x, b) + 1) {
        cout << "FILL(2)" << endl;
        go(x, b);
    } else if (f[x][y] == dfs(0, y) + 1) {
        cout << "DROP(1)" << endl;
        go(0, y);
    } else if (f[x][y] == dfs(x, 0) + 1) {
        cout << "DROP(2)" << endl;
        go(x, 0);
    } else {
        int t = min(a - x, y);
        if (f[x][y] == ④) {
            cout << "POUR(2,1)" << endl;
            go(x + t, y - t);
        } else {
            t = min(x, b - y);
            if (f[x][y] == ⑤) {
                cout << "POUR(1,2)" << endl;
                go(x - t, y + t);
            } else
                assert(0);
        }
    }
}

int main() {
    cin >> a >> b >> c;
    ans = 1 << 30;
    memset(f, 127, sizeof f);
    init = **f;
    if ((ans = dfs(0, 0)) == init - 1)
        cout << "impossible";
    else {
        cout << ans << endl;
        go(0, 0);
    }
}

第 39 题(单选题,3 分)

①处应填()

A.
dfs(x + t, y - t) + 1
B.
dfs(x + t, y - t) - 1
C.
dfs(x - t, y + t) + 1
D.
dfs(x - t, y + t) - 1

正确答案A

解析详情
答案 A状态转移:容器 2 倒入容器 1
思路与推导
  1. 当前容器水量为 (x,y),向容量 a 的容器 1 倒水,最多还能装 a−x,同时容器 2 只有 y 升可倒,因此 t=min(a−x,y)。

  2. 新状态为 (x+t,y−t),当前倒水消耗一步,所以①填 dfs(x+t,y−t)+1,选 A。

  3. 例如 a=5、x=2、y=4 时 t=3,新状态为 (5,1),不能把目标容器装到超过容量。

第 40 题(单选题,3 分)

②处应填()

A.
dfs(x + t, y - t) + 1
B.
dfs(x + t, y - t) - 1
C.
dfs(x - t, y + t) + 1
D.
dfs(x - t, y + t) - 1

正确答案C

解析详情
答案 C状态转移:容器 1 倒入容器 2
思路与推导
  1. 向容器 2 倒水时,可倒量同时受容器 1 的现存水量 x 和容器 2 的剩余容量 b−y 限制,故 t=min(x,b−y)。

  2. 倒完后容器 1 减少 t、容器 2 增加 t,新状态 (x−t,y+t),再加这一步操作,②选 C。

第 41 题(单选题,3 分)

③处应填()

A.
x == c || y == c
B.
x == c && y == c
C.
x >= c || y >= c
D.
x >= c && y >= c

正确答案A

解析详情
答案 A搜索终止条件
思路与推导
  1. 题目只要求任意一个容器中恰好有 c 升。到达 x=c 或 y=c 时已经成功,后面不需要继续输出操作。

  2. 所以③填 x==c || y==c,选 A。&& 会错误地要求两容器同时达到 c,>= 则会把多于 c 的水也当作完成。

第 42 题(单选题,3 分)

④处应填()

A.
dfs(x + t, y - t) + 1
B.
dfs(x + t, y - t) - 1
C.
dfs(x - t, y + t) + 1
D.
dfs(x - t, y + t) - 1

正确答案A

解析详情
答案 A最优路径还原
思路与推导
  1. 还原路径时,若记录的当前代价恰等于后继代价加 1,就可以输出这次操作并递归到后继。

  2. 本分支打印 POUR(2,1),后继明确为 (x+t,y−t),所以④必须与 dfs 中相应转移一致,填 dfs(x+t,y−t)+1,选 A。

  3. 这个等式解释的是路径还原规则;对于带环状态图,不能仅凭使用了记忆化 DFS 就一般性保证所有 f 值均为最短距离。

第 43 题(单选题,3 分)

⑤处应填()

A.
dfs(x + t, y - t) + 1
B.
dfs(x + t, y - t) - 1
C.
dfs(x - t, y + t) + 1
D.
dfs(x - t, y + t) - 1

正确答案C

解析详情
答案 C最优路径还原
思路与推导
  1. 最后一个分支对应 POUR(1,2),后继为 (x−t,y+t),故需比较 f[x][y] 与 dfs(x−t,y+t)+1,⑤选 C。

  2. 选择后就按代码进入该后继,每次把剩余操作数减一;方向和加一成本都必须与搜索转移保持一致。

  3. 若用它实现独立的最少步数求解,应另外证明带环图上的距离计算正确,通常可用 BFS 作可靠对照。