2022年CSP-S1提高级第一轮题解
一、单项选择(30分)
(共15题,每题2分,共计30分:每题有且仅有一个正确选项)
- 在 Linux 系统终端中,用于切换工作目录的命令为( )。
- A.
ls
- B.
cd
- C.
cp
- D.
all
答案为
cd
。
Linux中常用的跟目录和文件相关的命令有:
cd
:用于切换当前工作目录ls
:列出目录内容cp
:复制文件或目录pwd
:显示当前工作目录的绝对路径echo
:终端输出文本字符串或者变量的值,例如:echo "Hello, World!"
mkdir
:创建新目录rmdir
:删除空目录touch file.txt
# 创建名为 file.txt 的空文件rm -r non_empty_dir
# 递归删除目录(包括子目录和文件)
- 你同时用
time
命令和秒表为某个程序在单核 CPU 的运行计时。假如time
命令的输出如下:
real 0m30.721s
user 0m24.579s
sys 0m6.123s
以下最接近秒表计时的时长为( )。
- A.
30s
- B.
24s
- C.
18s
- D.
6s
time ls -l
标准输出包含三个时间值:
- real (实际时间/墙上时钟时间):从开始到结束的总时间,接近秒表计时
- user (用户CPU时间):在用户模式下花费的CPU时间
- sys (系统CPU时间):在内核模式下花费的CPU时间
- 若元素 a 、 b 、 c 、 d 、 e 、 f a、b、c、d、e、f a、b、c、d、e、f 依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次退栈操作,则不可能得到的出栈序列是( )。
- A.
dcebfa
- B.
cbdaef
- C.
bcaefd
- D.
afedcb
afedcb
:a
进,a
出,b
进,c
进,d
进,e
进,f
进,f
出,e
出,d
出,c
出,b
出。存在连续 3 3 3次退栈操作。
- 考虑对 n n n个数进行排序,以下最坏时间复杂度低于 O ( n 2 ) O(n^2) O(n2)的排序方法是( )
- A. 插入排序
- B. 冒泡排序
- C. 归并排序
- D. 快速排序
排序算法时间复杂度比较表
排序算法 | 最好时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 稳定 |
选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 |
插入排序 | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 稳定 |
希尔排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g 2 n ) O(n log^2 n) O(nlog2n) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 |
快速排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( n l o g n ) O(n log n) O(nlogn) | 不稳定 |
归并排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n ) O(n) O(n) | 稳定 |
堆排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( 1 ) O(1) O(1) | 不稳定 |
计数排序 | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | 稳定 |
桶排序 | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | O ( n 2 ) O(n^2) O(n2) | O ( n + k ) O(n + k) O(n+k) | 稳定 |
基数排序 | O ( n k ) O(nk) O(nk) | O ( n k ) O(nk) O(nk) | O ( n k ) O(nk) O(nk) | O ( n + k ) O(n + k) O(n+k) | 稳定 |
- 假设在基数排序过程中,受宇宙射线的影响,某项数据异变为一个完全不同的值。请问排序算法结束后,可能出现的最坏情况是( )。
- A. 移除受影响的数据后,最终序列是有序序列
- B. 移除受影响的数据后,最终序列是前后两个有序的子序列
- C. 移除受影响的数据后,最终序列是一个有序的子序列和一个基本无序的子序列
- D. 移除受影响的数据后,最终序列基本无序
基数排序(Radix Sort)是一种非比较型整数排序算法,其核心思想是通过将整数按位数逐位分配和收集,实现对数据的排序。它结合了计数排序或桶排序的思想,在保证稳定性的前提下,对每一位进行排序,最终实现整体有序。
核心原理:基数(Radix)表示进制数(如十进制基数为10,二进制基数为2)。每轮排序将数据分配到基数对应的桶中(如十进制需要10个桶)。然后将整数按每一位的值(个位、十位、百位等)依次排序,从最低位(LSD, Least Significant Digit)到最高位(MSD, Most Significant Digit)。
具体步骤:假设对数组 [170, 45, 75, 90, 802, 24, 2, 66] 进行排序
- 按个位排序:
- 桶0: 170, 90
- 桶2: 802, 2
- 桶4: 24
- 桶5: 45, 75
- 桶6: 66
- 合并桶得到:[170, 90, 802, 2, 24, 45, 75, 66]
- 按十位排序:
- 桶0: 802, 2
- 桶2: 24
- 桶4: 45
- 桶6: 66
- 桶7: 170, 75
- 桶9: 90
- 合并桶得到:[802, 2, 24, 45, 66, 170, 75, 90]
- 按百位排序:
- 桶0: 2, 24, 45, 66, 75, 90
- 桶1: 170
- 桶8: 802
- 合并桶得到:[2, 24, 45, 66, 75, 90, 170, 802]
由此可见,当某项数据发生变化,移除该数据后,最终序列是有序序列
- 计算机系统用小端(Little Endian)和大端(Big Endian)来描述多字节数据的存储地址顺序模式,其中小端表示将低位字节数据存储在低地址的模式、大端表示将高位字节数据存储在低地址的模式。在小端模式的系统和大端模式的系统分别编译和运行以下 C ++代码段表示的程序,将分别输出什么结果?( )
unsigned x = 0xDEADNEEF; unsigned char *p = (unsigned char *)&x; printf("%X", *p);
- A.
EF
、EF
- B.
EF
、DE
- C.
DE
、EF
- D.
DE
、DE
整数
x = 0xDEADBEEF
的十六进制表示为DE AD BE EF
,一共 4 4 4字节。
- 在小端模式(Little Endian)下,最低有效字节存储在低地址,内存中字节的实际排列(低地址 → 高地址)为
EF BE AD DE
。*p
指向x
的起始地址(即最低地址),取第一个字节的值0xEF
,按照十六进制格式输出EF
。- 大端模式(Big Endian)下,最高有效字节存储在低地址,因此内存中字节的排列(低地址 → 高地址)为:
DE AD BE EF
,按照十六进制格式输出DE
。
- 一个深度为 5 5 5(根结点深度为 1 1 1)的完全 3 3 3 叉树,按前序遍历的顺序给结点从 1 1 1 开始编号,则第 100 100 100 号结点的父结点是第( )号
- A. 95 95 95
- B. 96 96 96
- C. 97 97 97
- D. 98 98 98
根据等比数列求和公式,一个深度为 n n n的满三叉树的结点个数为 1 − 3 n 1 − 3 \frac{1-3^n}{1-3} 1−31−3n,因此:
- 2 2 2层的满三叉树的结点个数为 4 4 4
- 3 3 3层的满三叉树的结点个数为 13 13 13
- 4 4 4层的满三叉树的结点个数为 40 40 40
- 5 5 5层的满三叉树的结点个数为 121 121 121
那么题目要求的完全三叉树如下图所示:
- 强连通图的性质不包括( )
- A. 每个顶点的度数至少为 1
- B. 任意两个顶点之间都有边相连
- C. 任意两个顶点之间都有路径相连
- D. 每个顶点至少都连有一条边
强连通图(Strongly Connected Graph) 是指图中任意两个顶点
u
和v
,都存在从u
到v
的路径和从v
到u
的路径。注意是路径,也就是能到达,而不是一定存在边。
- 每个顶点度数均为 2 2 2 的无向图称为“ 2 2 2-正规图”。由编号为从 1 1 1 到 n n n 的顶点构成的所有 2 2 2-正规图,其中包含欧拉回路的不同 2 2 2-正规图的数量为( )。
- A. n ! n! n!
- B. ( n − 1 ) ! (n-1)! (n−1)!
- C. n ! 2 \frac{n!}{2} 2n!
- D. ( n − 1 ) ! 2 \frac{(n-1)!}{2} 2(n−1)!
2 2 2-正规图:每个顶点的度数均为 2 的无向图,这样的图由若干个不相交的环(cycles)构成。如下图所示:
欧拉回路:在一个图中,欧拉回路是指一条经过每一条边恰好一次的闭合路径(起点和终点相同)。对于无向图,存在欧拉回路的充要条件是:
- 图是连通的(即整个图是一个单一的环)。
- 所有顶点的度数均为偶数,如下图所示
问题转化:由于 2 2 2-正规图由若干个不相交的环构成,而欧拉回路要求图是连通的,因此只有当 2-正规图是一个单一的环(即整个图是一个大环)时,它才包含欧拉回路。那么,我们需要计算的是:由 n n n 个顶点构成的、单一的环的数量。
环的排列数:一个 n n n 个顶点的环可以看作是一个圆排列(circular permutation)。固定一个顶点(比如 1 1 1),剩下的 n − 1 n−1 n−1 个顶点可以排列成 ( n − 1 ) ! (n−1)! (n−1)! 种不同的顺序。但是,由于环可以顺时针或逆时针遍历(两种方向),例如 1 → 2 → 3 1\to2\to3 1→2→3和 1 → 3 → 2 1\to3\to2 1→3→2对应了同一种圆排列。所以总排列数为: ( n − 1 ) ! 2 \frac{(n-1)!}{2} 2(n−1)!
- 共有 8 8 8 人选修了程序设计课程,期末大作业要求由 2 2 2 人组成的团队完成。假设不区分每个团队内 2 2 2 人的角色和作用,请问共有多少种可能的组队方案。( )
- A. 28 28 28
- B. 32 32 32
- C. 56 56 56
- D. 64 64 64
从 8 8 8人中选 2 2 2人组队的方案数为 C ( 8 , 2 ) = 8 ! 6 ! × 2 ! C(8,2)=\frac{8!}{6!\times2!} C(8,2)=6!×2!8!,答案为 8 × 7 2 = 28 \frac{8\times7}{2}=28 28×7=28种
- 小明希望选到形如“省 A · LLDDD”的车牌号。车牌号在“ · ”之前的内容固定不变; 后面的 5 5 5 位号码中, 前 2 2 2 位必须是大写英文字母, 后 3 3 3 位必须是阿拉伯数字(L代表 A 至 Z,D 表示 0 0 0 至 9 9 9,两个L和三个D之间可能相同也可能不同)。请问总共有多少个可供选择的车牌号。( )
- A. 20280 20280 20280
- B. 52000 52000 52000
- C. 676000 676000 676000
- D. 1757600 1757600 1757600
- 字母的种类数量: 26 × 26 = 676 26×26=676 26×26=676
- 后三位数字的种类数量: 10 × 10 × 10 = 1000 10×10×10=1000 10×10×10=1000
- 总共有 676 × 1000 = 676000 676×1000=676000 676×1000=676000 多少个可供选择的车牌号。
- 给定地址区间为 0 ∼ 9 0\sim9 0∼9 的哈希表,哈希函数为 h ( x ) = x m o d 10 h(x)=x \mod10 h(x)=xmod10,采用线性探查的冲突解决策略(对于出现冲突情况,会往后探查第一个空的地址存储;若地址 9 9 9 冲突了则从地址 0 0 0 重新开始探查)。哈希表初始为空表,依次存储( 71 , 23 , 73 , 99 , 44 , 79 , 89 71,23,73,99,44,79,89 71,23,73,99,44,79,89)后,请问 89 89 89 存储在哈希表哪个地址中。( )
- A. 9 9 9
- B. 0 0 0
- C. 1 1 1
- D. 2 2 2
哈希表采用开放寻址法依次存储 71 , 23 , 73 , 99 , 44 , 79 , 89 71,23,73,99,44,79,89 71,23,73,99,44,79,89后,如下图所示:
- 对于给定的 n n 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) O(n)
- B. O ( n l o g n ) O(nlogn) O(nlogn)
- C. O ( n n ) O(n\sqrt{n}) O(nn)
- D. O ( n 2 ) O(n^2) O(n2)
内层循应该是写错了,陷入死循环。
for (j = 1; j <= n; j*=2)
执行 l o g n logn logn次,总体的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
- 以比较为基本运算,在 n n n 个数的数组中找最大的数,在最坏情况下至少要做( )次运算。
- A. n 2 \frac{n}{2} 2n
- B. n n n
- C. n + 1 n+1 n+1
- D. n − 1 n-1 n−1
假设第 1 1 1个数为最大的数,然后从第 2 2 2个数开始打擂台比较最大值,需 n − 1 n−1 n−1 次比较。
ack
函数在输入参数(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 5 5
- B. 7 7 7
- C. 9 9 9
- D. 13 13 13
打表模拟即可:
二、阅读程序(40分)
程序输入不超过数组成字符串定义的范围:判断题正确填√,错误填×;除特殊说明外,判断题1.5分,选择题3分,共计40分
阅读程序#1
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int f(const string &s, const string &t)
{
int n = s.length(), m = t.length();
vector<int> shift(128, m + 1);
int i, j;
for (j = 0; j < m; j++)
shift[t[j]] = m - j;
for (i = 0; i <= n - m; i += shift[s[i + m]]) {
j = 0;
while (j < m && s[i + j] == t[j]) j++;
if (j == m) return i;
}
return -1;
}
int main() {
string a, b;
cin >> a >> b;
cout << f(a, b) << endl;
return 0;
}
假设输入字符串由 ASCII 可见字符组成。
程序解析
在f(s, t)
函数中:
vector<int> shift(128, m + 1);
,初始化ASCII表的默认跳转步长,将每个字符跳转步长初始化为m + 1
shift[t[j]] = m - j;
记录字符串t
中每个字符t[j]
到右端的距离,例如:t = "abc"
shift['a'] = 3
shift['b'] = 2
shift['c'] = 1
while (j < m && s[i + j] == t[j]) j++;
, 如果对应位置的字符相同,则继续判断下一个字符。if (j == m) return i;
,如果匹配成功,返回匹配位置。- 如果当前位置匹配失败,
i += shift[s[i + m]]
,例如s = "ababc", t = "abc"
:- 第一次匹配失败后,
i += shift[s[0 + 3]]
shift['b'] = 2
- 从位置
2
继续匹配
- 第一次匹配失败后,
- 匹配失败后,返回
-1
- 当输入为
abcde fg
时,输出为-1
。( )
- A. 正确
- B. 错误
根据程序解析,匹配失败后返回
-1
。
- 当输入为
abbababbbab abab
时,输出为4
。()
- A. 正确
- B. 错误
根据程序解析,匹配成功后,返回
abab
在abbababbbab
第一次出现的位置,答案应该为3
。
- 当输入为
GoodLuckCsp2022 22
时,第 20 20 20 行的j++
语句执行次数为 2 2 2。()
- A. 正确
- B. 错误
s = "GoodLuckCsp2022", t = "22", n = 14, m = 2, shift['2'] = 1
,执行过程如下图所示:
当i=13
时匹配成功,此时j ++
语句被执行 2 2 2次。
- 该算法最坏情况下的时间复杂度为( )。
- A. O ( n + m ) O(n+m) O(n+m)
- B. O ( n l o g m ) O(nlogm) O(nlogm)
- C. O ( m l o g n ) O(mlogn) O(mlogn)
- D. O ( n m ) O(nm) O(nm)
- 最好情况:当主串
s
中存在大量模式串t
未包含的字符时,每次会跳过长度为 m m m的子串,时间复杂度为 O ( n / m ) O(n/m) O(n/m)- 最坏情况:每次匹配失败时仅能滑动 1 1 1个位置,且每次比较需完整遍历模式串所有字符,例如:
s = "AAAAAA", t = "AAAAB"
,时间复杂度为 O ( n m ) O(nm) O(nm)
f(a, b)
与下列( )语句的功能最类似。
- A.
a.find(b)
- B.
a.rfind(b)
- C.
a.substr(b)
- D.
a.compare(b)
根据根据程序解析,
f(a, b)
的功能是在字符串a
中按照从左向右的方向匹配b
,如果找到,返回b
第一次出现的位置;否则没找到返回-1
。与a.find(b)
功能类似。
a.rfind(b)
是从字符串a
的右侧开始匹配b
,并返回其在字符串a
中的位置。a.substr(idx, length)
返回字符串a
中从idx
位置开始长度为legth
的子串a.compare(b)
,比较字符串a
和a
和b
,如果两个串值相同,则函数返回0
;若字符串a
按字典顺序要先于b
,则返回负值;反之,则返回正值
- 当输入为
baaabaaabaaabaaaa aaaa
,第 20 行的j++
语句执行次数为 ( )。
- A. 9 9 9
- B. 10 10 10
- C. 11 11 11
- D. 12 12 12
s = "baaabaaabaaabaaaa" , t = "aaaa", n = 17, m = 4, shift['a'] = 1
,执行过程如下:
i = 0
时,匹配失败,i += shift[s[0 + 4]]
,i += shift['b']
,i = 5
i = 5
时,匹配成功,j++
执行 3 3 3次,i += shift[s[5 + 4]]
,i += shift['a']
,i = 6
i = 6
时,匹配成功,j++
执行 2 2 2次,i += shift[s[6 + 4]]
,i += shift['a']
,i = 7
i = 7
时,匹配成功,j++
执行 1 1 1次,i += shift[s[7 + 4]]
,i += shift['a']
,i = 8
i = 8
时,匹配失败,i += shift[s[8 + 4]]
,i += shift['b']
,i = 13
i = 13
时,匹配成功,j++
执行 4 4 4次,返回13
阅读程序#2
#include <iostream>
using namespace std;
const int MAXN = 105;
int n, m, k, val[MAXN];
int temp[MAXN], cnt[MAXN];
void init()
{
cin >> n >> k;
for (int i = 0; i < n; i++) cin >> val[i];
int maximum = val[0];
for (int i = 1; i < n; i++)
if (val[i] > maximum) maximum = val[i];
m = 1;
while (maximum >= k) {
maximum /= k;
m++;
}
}
void solve()
{
int base = 1;
for (int i = 0; i < m; i++) {
for (int j = 0; j < k; j++) cnt[j] = 0;
for (int j = 0; j < n; j++) cnt[val[j] / base % k]++;
for (int j = 1; j < k; j++) cnt[j] += cnt[j - 1];
for (int j = n - 1; j >= 0; j--) {
temp[cnt[val[j] / base % k] - 1] = val[j];
cnt[val[j] / base % k]--;
}
for (int j = 0; j < n; j++) val[j] = temp[j];
base *= k;
}
}
int main()
{
init();
solve();
for (int i = 0; i < n; i++) cout << val[i] << ' ';
cout << endl;
return 0;
}
假设输入的n
为不大于
100
100
100 的正整数,k
为不小于
2
2
2 且不大于
100
100
100 的正整数,val[i]
在
int
表示范围内,完成下面的判断题和单选题。
程序解析
init()
输入n
和k
,以及 n n n个正整数 v a l i val_i vali。打擂台求val[i]
的最大值maximum
,并求得maximum
在k
进制下的位数m
solve()
按照k
进制对val[]
进行基数排序,每次循环处理一位cnt[val[j] / base % k]++;
,统计当前位的出现频次cnt[j] += cnt[j-1];
,计算前缀和,确定位置for(int j=n-1; j>=0; j--)
,从后向前遍历,保证排序的稳定性temp[cnt[val[j] / base % k] - 1] = val[j];
将待排序元素放到临时数组的正确位置
base *= k;
,更新基数位权重。base
表示k
进制下当前位的权重,val[j] / base % k
求val[j]
的当前位。
- 这是一个不稳定的排序算法。( )
- A. 正确
- B. 错误
根据程序解析,在 31 31 31行从后向前遍历每个数,
for(int j=n-1; j>=0; j--)
,保证了排序的稳定性。
- 该算法的空间复杂度仅与 n n n 有关。( )
- A. 正确
- B. 错误
在程序中,除了使用
temp[]
临时存储排序好的关键字,还使用了cnt[]
记录在k
进制下每个基数的出现频次,因此空间复杂度为 O ( n + k ) O(n+k) O(n+k)
- 该算法的时间复杂度为 O ( m ( n + k ) ) O(m(n+k)) O(m(n+k))。( )
- A. 正确
- B. 错误
根据程序分析,在
solve()
中循环m
次,每次循环处理一位:
- 循环 n n n次,对于每个
val[i]
求出当前位的出现次数- 循环 k k k次,遍历每个基数计算前缀和,方便确定关键字的位置
总的时间复杂度为: O ( m ( n + k ) ) O(m(n+k)) O(m(n+k))
- 当输入为
5 3 98 26 91 37 46
时,程序第一次执行到第 36 36 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
按照三进制进行基数排序,第一轮 m o d 3 \mod 3 mod3的结果是: 98 m o d 3 = 2 , 26 m o d 3 = 2 , 91 m o d 3 = 1 , 37 m o d 3 = 1 , 46 m o d 3 = 1 98\mod 3 = 2,26\mod 3 = 2, 91\mod 3 = 1,37\mod 3 = 1,46\mod 3 = 1 98mod3=2,26mod3=2,91mod3=1,37mod3=1,46mod3=1,按照当前位排序后为
91 37 46 98 26
- 若
val[i]
的最大值为100
,k
取( )时算法运算次数最少。
- A. 2 2 2
- B. 3 3 3
- C. 10 10 10
- D. 不确定
根据程序分析,该算法的时间复杂度为 O ( m ( n + k ) ) O(m(n+k)) O(m(n+k))
k | m | 当n = 10 时总次数 | 当n = 100 时总次数 |
---|---|---|---|
2 | 7 | 7 × ( 10 + 2 ) = 84 7\times(10+2)=84 7×(10+2)=84 | 7 × ( 100 + 2 ) = 714 7\times(100+2)=714 7×(100+2)=714 |
3 | 5 | 5 × ( 10 + 3 ) = 65 5\times(10+3)=65 5×(10+3)=65 | 5 × ( 100 + 3 ) = 515 5\times(100+3)=515 5×(100+3)=515 |
10 | 3 | 3 × ( 10 + 10 ) = 60 3\times(10+10)=60 3×(10+10)=60(少) | 3 × ( 100 + 10 ) = 330 3\times(100+10)=330 3×(100+10)=330(少) |
从上表可以看出,上述选项中
k
取 10 10 10时算法运算次数最少。
- 当输入的
k
比val[i]
的最大值还大时,该算法退化为( )算法。
- A. 选择排序
- B. 冒泡排序
- C. 计数排序
- D. 桶排序
当
k > maximum
时,m = 1
,只处理一位,cnt[val[j] / base % k]++;
相等于cnt[val[j]]++;
统计了每个关键字的出现次数,算法退化为计数排序。
阅读程序#3
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXL = 1000;
int n, k, ans[MAXL];
int main(void)
{
cin >> n >> k;
if (!n) cout << 0 << endl;
else
{
int m = 0;
while (n)
{
ans[m++] = (n % (-k) + k) % k;
n = (ans[m - 1] - n) / k;
}
for (int i = m - 1; i >= 0; i--)
cout << char(ans[i] >= 10 ?
ans[i] + 'A' - 10 :
ans[i] + '0');
cout << endl;
}
return 0;
}
假设输入的 n
在 int
范围内,k
为不小于
2
2
2 且不大于
36
36
36 的正整数,完成下面的判断题和
单选题。
程序解析:该代码的核心功能是将整数n
转换为-k
进制的字符串表示,只要n != 0
,重复执行:
ans[m++] = (n % (-k) + k) % k;
,将每一步得到的余数存到到a[m]
,保证转换后的每一位为非负数。n = (ans[m - 1] - n) / k;
,根据除法计算公式:n = -k * 商 + 余数
,将n
更新为(ans[m - 1] - n) / k
,其中ans[m - 1]
表示当前的余数。- 最后,将得到结果转换为字符,逆序输出
- 该算法的时间复杂度为 O ( l o g k n ) O(log_kn) O(logkn)。( )
- A. 正确
- B. 错误
根据程序解析,负基数的进制转换的时间复杂度为 O ( l o g k n ) O(log_kn) O(logkn)。
- 删除第 23 23 23 行的强制类型转换,程序的行为不变。( )
- A. 正确
- B. 错误
第 23 23 23行的强制类型转换保证输出为字符,去掉后将输出字符对应的ASCII码。
- 除非输入的
n
为0
,否则程序输出的字符数为 Θ ( ⌊ l o g k ∣ n ∣ ⌋ + 1 ) Θ(⌊log_k∣n∣⌋+1) Θ(⌊logk∣n∣⌋+1)。( )
- A. 正确
- B. 错误
在进行负基数的进制转化时,根据除法计算公式:
n = -k * 商 + 余数
,当n
为负数时,n = (余数 - n) / k
,n
的绝对值更大了,因此得到的位数会相对增加。例如:-1
对应的-2
进制数为 ( 11 ) − 2 (11)_{-2} (11)−2。
- 当输入为
100 7
时,输出为( )。
- A. 202 202 202
- B. 151 151 151
- C. 244 244 244
- D. 1754 1754 1754
模拟计算:
- a[0] = 100 % 7 =
2
, n = (2 - 100) / 7 = -14- a[1] = -14 % 7 =
0
, n = (0 + 14) / 7 = 2- a[2] = 2 % 7 =
2
, n = (2 - 2) / 7 = 0
- 当输入为
-255 8
时,输出为( )。
- A. 1400 1400 1400
- B. 1401 1401 1401
- C. 417 417 417
- D. 400 400 400
模拟计算:
- a[0] = -255 % 8 =
1
, n = (1 + 255) / 8 = 32- a[1] = 32 % 8 =
0
, n = (0 - 32) / 8 = -4- a[2] = -4 % 8 =
4
, n = (4 + 4) / 8 = 1- a[3] = 1 % 8 =
1
, n = (1 - 1) / 8 = 0
- 当输入为
1000000 19
时,输出为( )。
- A.
BG939
- B.
87GIB
- C.
1CD428
- D.
7CF1B
模拟计算:
- a[0] = 1000000 % 19 = 11 =
B
, n = (11 - 1000000) / 19 = -52631- a[1] = -52631 % 19 = 18 =
I
, n = (18 + 52631) / 19 = 2771- a[2] = 2771 % 19 = 16 =
G
, n = (16 - 2771) / 19 = -145- a[3] = -145 % 19 =
7
, n = (7 + 145) / 19 = 8- a[4] = 8 % 19 =
8
, n = (8 - 8) / 19 = 0
三、完善程序(30分)
单选题,每小题3分,共计 30 分
完善程序#1
(归并第
k
k
k 小)已知两个长度均为
n
n
n 的有序数组
a
1
a1
a1 和
a
2
a2
a2(均为递增序,但不保证严
格单调递增),并且给定正整数
k
k
k(
1
≤
k
≤
2
n
1≤k≤2n
1≤k≤2n),求数组
a
1
a1
a1 和
a
2
a2
a2 归并排序后的数组里
第
k
k
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);
}
}
}
程序解析:通过对两个升序数组
a
1
a1
a1和
a
2
a2
a2进行双数组二分查找,来寻找第k
小的元素,基本过程如下:
- 同时维护两个数组的二分查找区间,
[left1,right1]
和[left2,right2]
。 - 每次循环比较两个数组中间元素的大小关系,通过判断元素个数
cnt
与k
的关系,调整二分区间边界来逼近目标位置 - 最终合并两个数组的有效区间计算结果
- ① 处应填( )。
- A.
(m1 + m2) * 2
- B.
(m1 - 1) + (m2 - 1)
- C.
m1 + m2
- D.
(m1 + 1) + (m2 + 1)
cnt
表示从0
到 a 1 a1 a1数组中间位置m1
、以及从0
到 a 2 a2 a2数组中间位置m2
上,元素总的个数,因此cnt = m1 +1 + m2 + 1;
。①处应填(m1 + 1) + (m2 + 1)
。
注意:C选项m1 + m2
也是可以的,对算法的正确性没有影响。
- ② 处应填( )。
- A.
a1[m1] == a2[m2]
- B.
a1[m1] <= a2[m2]
- C.
a1[m1] >= a2[m2]
- D.
a1[m1] != a2[m2]
通过比较两个数组中间元素的大小,动态调整搜索区间:
- 当
a1[m1]
比较小时,
- 元素个数
cnt < k
时,扩展a1
的搜索范围,left1 = m1 + 1;
- 元素个数
cnt >= k
时,缩小a2
的搜索范围,排除过大元素,right2 = m2 - 1;
- 当
a1[m1] >= a2[m2]
时,操作类似
- 元素个数
cnt < k
时,扩展a2
的搜索范围,left2 = m2 + 1;
- 元素个数
cnt >= k
时,缩小a1
的搜索范围,排除过大元素,right1 = m1 - 1;
② 处比较两个数组中间元素的大小,应填
a1[m1] <= a2[m2]
- ③ 处应填( )。
- A.
left1 == right1
- B.
left1 < right1
- C.
left1 > right1
- D.
left1 != right1
二分查找结束后,判断哪个数组的搜索区间耗尽
- 当
left1 > right1
,说明 a 1 a1 a1数组的搜索区间耗尽
- 当
left1 == 0
时,说明 a 1 a1 a1数组元素都大于等于 a 2 a2 a2,并且第 k k k小元素在 a 2 a2 a2数组中- 否则,取搜索结束时,两数组交叉处的最大值
- 当
left2 > right2
,说明 a 2 a2 a2数组的搜索区间耗尽,操作类似③ 处判断是否为 a 1 a1 a1数组的搜索区间耗尽,应填
left1 > right1
。
- ④ 处应填( )。
- A.
y = a1[k - left2 - 1]
- B.
y = a1[k - left2]
- C.
y = a2[k - left1 - 1]
- D.
y = a2[k - left1]
当 a 1 a1 a1数组的搜索区间耗尽时,并且
left1 != 0
,说明第 k k k小元素在两个数组的“交叉处”,并且 a 1 a1 a1数组中的0
到left1
位置上的元素都k
以内,这里y
应取 a 2 a2 a2数组中第k - left1 - 1
位置上的数,因此④ 处应填y = a2[k - left1 - 1]
。
- ⑤ 处应填( )。
- A.
y = a1[k - left2 - 1]
- B.
y = a1[k - left2]
- C.
y = a2[k - left1 - 1]
- D.
y = a2[k - left1]
与④出原理一样,
y
应取 a 1 a1 a1数组中第k - left2 - 1
位置上的数,因此⑤ 处应填y = a1[k - left2 - 1]
。
完善程序#2
(容器分水)有两个容器,容器 1 1 1 的容量为为 a a a 升,容器 2 2 2 的容量为 b b b 升;同时允许下列的三种操作,分别为:
FILL(i)
:用水龙头将容器 i i i( i ∈ 1 , 2 i∈{1,2} i∈1,2)灌满水;DROP(i)
:将容器 i i i 的水倒进下水道;POUR(i,j)
:将容器 i i i 的水倒进容器 j j j(完成此操作后,要么容器 j 被灌满,要么容器 i i i 被清空)。
求只使用上述的两个容器和三种操作,获得恰好 c c c 升水的最少操作数和操作序列。上述 a a a、 b b b、 c c c 均为不超过 100 100 100 的正整数,且 c ≤ m a x { a , b } c≤max\{a,b\} c≤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);
}
}
程序解析
- 在
main()
,将f[][]
按字节初始化为最大值,并将init
初始化为最大值-1
- 通过记忆化搜索
dfs(x, y)
,计算当容器 1 1 1得到x
升水、容器 2 2 2得到y
升水的最少操作次数f[x][y]
- 当有一个容器有
c
升水时,即x == c || y == c
,搜索结束 - 将
f[x][y]
设置为init - 1
,暴力搜索 6 6 6种操作,取到达f[x][y]
的最少操作次数f[x][y] = min(f[x][y], dfs(a, y) + 1);
,FILL(1)
操作:将容器 1 1 1灌a
升水f[x][y] = min(f[x][y], dfs(x, b) + 1);
,FILL(2)
操作:将容器 2 2 2灌``升水f[x][y] = min(f[x][y], dfs(0, y) + 1);
,DROP(1)
操作:将容器 1 1 1的水倒进下水道f[x][y] = min(f[x][y], dfs(x, 0) + 1);
,DROP(2)
操作:将容器 2 2 2的水倒进下水道f[x][y] = min(f[x][y], dfs(x + t, y - t) + 1);
,POUR(2, 1)
,将容器 2 2 2的水倒入容器 1 1 1f[x][y] = min(f[x][y], dfs(x - t, y + t) + 1);
,POUR(1, 2)
,将容器 1 1 1的水倒入容器 2 2 2
- 当有一个容器有
- 通过
go(x, y)
,递归输出操作序列- 当有一个容器有
c
升水时,即x == c || y == c
,递归结束 - 如果当前状态的操作次数,是在上一阶段的某个状态转移过来时,输出操作,递归处理上一个阶段。
- 当有一个容器有
- ① 处应填( )。
- 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
根据程序解析,首先计算容器 1 1 1剩余容量,即
a - x
,以及容器 2 2 2当前容量y
,取两者的最小值,即t = min(a - x, y);
,进行POUR(2, 1)
操作,将容器 2 2 2的水倒入容器 1 1 1,然后递归计算下一阶段状态,因此① 处应填dfs(x + t, y - t) + 1
- ② 处应填( )。
- 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
根据程序解析,首先计算容器 1 1 1当前容量
x
,以及容器 2 2 2剩余容量,即b - y
,取两者的最小值,即t = min(x, b - y);
,进行POUR(1, 2)
操作,将容器 1 1 1的水倒入容器 2 2 2,然后递归计算下一阶段状态,因此② 处应填dfs(x - t, y + t) + 1
- ③ 处应填( )。
- A.
x == c || y == c
- B.
x == c && y == c
- C.
x >= c || y >= c
- D.
x >= c && y >= c
根据程序解析,当有一个容器有
c
升水时,递归结束,③ 处应填x == c || y == c
,
- ④ 处应填( )。
- 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
根据程序解析,要进行
POUR(2, 1)
操作,那么递归处理下一阶段应该是dfs(x + t, y - t)
,④ 处应填dfs(x + t, y - t) + 1
- ⑤ 处应填( )。
- 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
根据程序解析,要进行
POUR(1, 2)
操作,那么递归处理下一阶段应该是dfs(x - t, y + t)
,⑤ 处应填dfs(x - t, y + t) + 1