2022年CSP-S1提高级第一轮题解

一、单项选择(30分)

(共15题,每题2分,共计30分:每题有且仅有一个正确选项)

  1. 在 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 # 递归删除目录(包括子目录和文件)
  1. 你同时用 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时间
  1. 若元素 a 、 b 、 c 、 d 、 e 、 f a、b、c、d、e、f abcdef 依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次退栈操作,则不可能得到的出栈序列是( )。
  • A. dcebfa
  • B. cbdaef
  • C. bcaefd
  • D. afedcb

afedcba进,a出,b进,c进,d进,e进,f进,f出,e出,d出,c出,b出。存在连续 3 3 3次退栈操作。

  1. 考虑对 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)稳定
  1. 假设在基数排序过程中,受宇宙射线的影响,某项数据异变为一个完全不同的值。请问排序算法结束后,可能出现的最坏情况是( )。
  • 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]

由此可见,当某项数据发生变化,移除该数据后,最终序列是有序序列

  1. 计算机系统用小端(Little Endian)和大端(Big Endian)来描述多字节数据的存储地址顺序模式,其中小端表示将低位字节数据存储在低地址的模式、大端表示将高位字节数据存储在低地址的模式。在小端模式的系统和大端模式的系统分别编译和运行以下 C ++代码段表示的程序,将分别输出什么结果?( )
    unsigned x = 0xDEADNEEF;
    unsigned char *p = (unsigned char *)&x;
    printf("%X", *p);
    
  • A. EFEF
  • B. EFDE
  • C. DEEF
  • D. DEDE

整数 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
  1. 一个深度为 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} 1313n,因此:

  • 2 2 2层的满三叉树的结点个数为 4 4 4
  • 3 3 3层的满三叉树的结点个数为 13 13 13
  • 4 4 4层的满三叉树的结点个数为 40 40 40
  • 5 5 5层的满三叉树的结点个数为 121 121 121

那么题目要求的完全三叉树如下图所示:
在这里插入图片描述

  1. 强连通图的性质不包括( )
  • A. 每个顶点的度数至少为 1
  • B. 任意两个顶点之间都有边相连
  • C. 任意两个顶点之间都有路径相连
  • D. 每个顶点至少都连有一条边

强连通图(Strongly Connected Graph) 是指图中任意两个顶点uv,都存在从uv的路径和从vu的路径。注意是路径,也就是能到达,而不是一定存在边。

  1. 每个顶点度数均为 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)! (n1)!
  • C. n ! 2 \frac{n!}{2} 2n!
  • D. ( n − 1 ) ! 2 \frac{(n-1)!}{2} 2(n1)!

2 2 2-正规图:每个顶点的度数均为 2 的无向图,这样的图由若干个不相交的环(cycles)构成。如下图所示:

在这里插入图片描述

欧拉回路:在一个图中,欧拉回路是指一条经过每一条边恰好一次的闭合路径(起点和终点相同)。对于无向图,存在欧拉回路的充要条件是:

  • 图是连通的(即整个图是一个单一的环)。
  • 所有顶点的度数均为偶数,如下图所示

在这里插入图片描述

问题转化:由于 2 2 2-正规图由若干个不相交的环构成,而欧拉回路要求图是连通的,因此只有当 2-正规图是一个单一的环(即整个图是一个大环)时,它才包含欧拉回路。那么,我们需要计算的是: n n n 个顶点构成的、单一的环的数量
环的排列数:一个 n n n 个顶点的环可以看作是一个圆排列(circular permutation)。固定一个顶点(比如 1 1 1),剩下的 n − 1 n−1 n1 个顶点可以排列成 ( n − 1 ) ! (n−1)! (n1)! 种不同的顺序。但是,由于环可以顺时针或逆时针遍历(两种方向),例如 1 → 2 → 3 1\to2\to3 123 1 → 3 → 2 1\to3\to2 132对应了同一种圆排列。所以总排列数为: ( n − 1 ) ! 2 \frac{(n-1)!}{2} 2(n1)!

  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

  1. 小明希望选到形如“省 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 多少个可供选择的车牌号。
  1. 给定地址区间为 0 ∼ 9 0\sim9 09 的哈希表,哈希函数为 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后,如下图所示:
在这里插入图片描述

  1. 对于给定的 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)

  1. 以比较为基本运算,在 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 n1

假设第 1 1 1个数为最大的数,然后从第 2 2 2个数开始打擂台比较最大值,需 n − 1 n−1 n1 次比较。

  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
  1. 当输入为abcde fg时,输出为 -1。( )
  • A. 正确
  • B. 错误

根据程序解析,匹配失败后返回-1

  1. 当输入为abbababbbab abab时,输出为4。()
  • A. 正确
  • B. 错误

根据程序解析,匹配成功后,返回abababbababbbab 第一次出现的位置,答案应该为3

  1. 当输入为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次。

  1. 该算法最坏情况下的时间复杂度为( )。
  • 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)
  1. 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),比较字符串aab,如果两个串值相同,则函数返回 0;若字符串 a 按字典顺序要先于 b,则返回负值;反之,则返回正值
  1. 当输入为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 3i += shift[s[5 + 4]]i += shift['a']i = 6
  • i = 6时,匹配成功,j++执行 2 2 2i += shift[s[6 + 4]]i += shift['a']i = 7
  • i = 7时,匹配成功,j++执行 1 1 1i += 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() 输入nk,以及 n n n个正整数 v a l i val_i vali。打擂台求val[i]的最大值maximum,并求得maximumk进制下的位数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 % kval[j]的当前位。
  1. 这是一个不稳定的排序算法。( )
  • A. 正确
  • B. 错误

根据程序解析,在 31 31 31行从后向前遍历每个数,for(int j=n-1; j>=0; j--),保证了排序的稳定性。

  1. 该算法的空间复杂度仅与 n n n 有关。( )
  • A. 正确
  • B. 错误

在程序中,除了使用temp[]临时存储排序好的关键字,还使用了cnt[]记录在k进制下每个基数的出现频次,因此空间复杂度为 O ( n + k ) O(n+k) O(n+k)

  1. 该算法的时间复杂度为 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))

  1. 当输入为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=226mod3=291mod3=137mod3=146mod3=1,按照当前位排序后为91 37 46 98 26

  1. val[i]的最大值为 100k 取( )时算法运算次数最少。
  • 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))

kmn = 10时总次数n = 100时总次数
27 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
35 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
103 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时算法运算次数最少。

  1. 当输入的 kval[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;
}

假设输入的 nint 范围内,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]表示当前的余数。
  • 最后,将得到结果转换为字符,逆序输出
  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)

  1. 删除第 23 23 23 行的强制类型转换,程序的行为不变。( )
  • A. 正确
  • B. 错误

23 23 23行的强制类型转换保证输出为字符,去掉后将输出字符对应的ASCII码。

  1. 除非输入的 n0,否则程序输出的字符数为 Θ ( ⌊ l o g k ∣ n ∣ ⌋ + 1 ) Θ(⌊log_k∣n∣⌋+1) Θ(⌊logkn+1)。( )
  • A. 正确
  • B. 错误

在进行负基数的进制转化时,根据除法计算公式:n = -k * 商 + 余数,当n为负数时,n = (余数 - n) / kn的绝对值更大了,因此得到的位数会相对增加。例如:-1对应的-2进制数为 ( 11 ) − 2 (11)_{-2} (11)2

  1. 当输入为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
  1. 当输入为-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
  1. 当输入为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 1k2n),求数组 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]
  • 每次循环比较两个数组中间元素的大小关系,通过判断元素个数cntk的关系,调整二分区间边界来逼近目标位置
  • 最终合并两个数组的有效区间计算结果
  1. ① 处应填( )。
  • 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也是可以的,对算法的正确性没有影响。

  1. ② 处应填( )。
  • 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]

  1. ③ 处应填( )。
  • 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

  1. ④ 处应填( )。
  • 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数组中的0left1位置上的元素都k以内,这里y应取 a 2 a2 a2数组中第k - left1 - 1位置上的数,因此④ 处应填 y = a2[k - left1 - 1]

  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 升;同时允许下列的三种操作,分别为:

  1. FILL(i):用水龙头将容器 i i i i ∈ 1 , 2 i∈{1,2} i1,2)灌满水;
  2. DROP(i):将容器 i i i 的水倒进下水道;
  3. 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\} cmax{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 1a升水
      • 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 1
      • f[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,递归结束
    • 如果当前状态的操作次数,是在上一阶段的某个状态转移过来时,输出操作,递归处理上一个阶段。
  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剩余容量,即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

  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

  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

  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(2, 1)操作,那么递归处理下一阶段应该是dfs(x + t, y - t),④ 处应填dfs(x + t, y - t) + 1

  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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值