[CSP-J 2021]答案&解析

写在前面

作者马上要考CSP-J了,刷点历年真题,
特出此篇,帮助自己各位。
加粗字体代表答案,标记字体代表重点。
(作者近期非常高产)

题面

很抱歉,本博客仅仅提供讲解,
有需要的同学可以前往刷题网站自主练习。
洛谷有题

题解

答案合集(表格)

题号
1~5DBACD
6~10DCABB
11~15BACBB
16~20错误错误错误正确错误
21~25B错误正确正确B
26~30BC正确错误错误
31~35ACCDC
36~40CDBBD
41~45CBD

单项选择题

1

以下不属于面向对象程序设计语言的是( )。
A. C++
B. Python
C. Java
D. C

C语言属于面向过程的编程语言。

2

以下奖项与计算机领域最相关的是( )。
A. 奥斯卡奖
B. 图灵奖
C. 诺贝尔奖
D. 普利策奖

图灵奖是计算机领域的。

3

目前主流的计算机储存数据最终都是转换成( )数据进行储存。
A. 二进制
B. 十进制
C. 八进制
D. 十六进制

计算机存储数据采用二进制

4

以比较作为基本运算,在 N 个数中找出最大数,最坏情况下所需要的最少的比较次数为 ( )。
A. N2
B. N
C. N - 1
D. N + 1

采用打擂台的方式,设第一个数为maxn,依次比较后取最大值,比较次数为 N - 1 次。

5

对于入栈顺序为 a,b,c,d,e 的序列,下列( )不是合法的出栈序列。
A. a,b,c,d,e
B. e,d,c,b,a
C. b,a,c,d,e
D. c,d,a,e,b

c,d 出栈后,栈内元素从栈底至栈顶分别为 b,a,所以 a 必然在 b 后出栈,D选项不符。

6

对于有 n 个顶点、m 条边的无向连通图 (m>n),需要删掉( )条边才能使其成为一棵树
A. n−1
B. m−n
C. m−n−1
D. m−n+1

根据题意,树的边数为 n - 1,故去掉m - (n - 1)条边。

7

二进制数 101.11 对应的十进制数是( )。
A. 6.5
B. 5.5
C. 5.75
D. 5.25

进制转化:
1 * 22 + 0 * 21+ 1 * 20 + 1 * 2-1 + 1 * 2-2 = 4 + 1 + 0.5 + 0.25 = 5.75

8

如果一棵二叉树只有根结点,那么这棵二叉树高度为
1。请问高度为 5 的完全二叉树有 ( )种不同的形态?
A. 16
B. 15
C. 17
D. 32

高度为5的完全二叉树最后一层的节点数为1~16

9

9.表达式a * (b + c) * d的后缀表达式为(),其中“ * ”和“ + ”是运算符。
A. * * a+bcd
B. abc+* d *
C. abc+d * *
D. * a *+bcd

A选项和D选项都是前缀表达式,B选项和C选项是后缀表达式。
因为后缀表达式为两个操作数在前,操作符在后的形式
所以用栈模拟一下就可以得出abc + * d *。

10

6 个人,两个人组一队,总共组成三队,不区分队伍的编号。不同的组队情况有( )种。
A. 10
B. 15
C. 30
D. 20

一共有 C62 * C42 * C22种组合方案,由于“不区分队伍的编号”,所以除以A33
最终组队情况为((6 * 5) / 2 * (4 * 3) / 2 * (2 * 1) / 2) / (3 * 2) = 15种。

11

在数据压缩编码中的哈夫曼编码方法,在本质上是一种( )的策略。
A. 枚举
B. 贪心
C. 递归
D. 动态规划

哈夫曼编码每次把频率最低的两个节点合并,产生新的节点并放在集合中,删除用来合并的两个节点,重复这个过程直到剩下一个节点为止。每次选择频率最小的两个节点就是贪心的思想

12

由 1,1,2,2,3 这五个数字组成不同的三位数有( )种。
A. 18
B. 15
C. 12
D. 24

暴力枚举即可~:321,322,311,312,211,212,213, 221, 223,231, 232, 112,113,121,122,123,131,132。总计 18 个

13

13.考虑如下递归算法

solve(n)
    if n<=1 return 1
    else if n>=5 return n*solve(n-2)
    else return n*solve(n-1)

则调用 solve(7) 得到的返回结果为( )。
A. 105
B. 840
C. 210
D. 420

按照递归算法的要求递归即可:
solve(7) = 7 * solve(5) = 7 * 5 * solve(3) = 7 * 5 * 3 * solve(2) = 7 * 5 * 3 * 2 * sovle(1) = 7 * 5 * 3 * 2 * 1 = 210

14

以 a 为起点,对下边的无向图进行深度优先遍历,则 b,c,d,e 四个点中有可能作为最后一个遍历到的点的个数为( )。
14题图例
A. 1
B. 2
C. 3
D. 4

第一种:a-b-d-c-e
第二种:a-c-e-d-b
第三种:a-c-d-b-e
故最后遍历到的点数的个数为2

15

有四个人要从 A 点坐一条船过河到 B 点,船一开始在 A 点。该船一次最多可坐两个人。 已知这四个人中每个人独自坐船的过河时间分别为 1,2,4,8,且两个人坐船的过河时间为两人独自过河时间的较大者。则最短( )时间可以让四个人都过河到 B 点(包括从 B 点把船开回 A 点的时间)。
A. 14
B. 15
C. 16
D. 17

过河策略:
1.让较快的送较慢的,然后较快的人返回。
2.让两个较慢的人一起过河。
最优过河方案之一:
1,2 → 过河 ← 1 返回
4,8 → 过河 ← 2 返回
1,2 → 过河
总耗时:2 + 1 + 8 + 2 + 2 = 15

阅读程序题

01 #include <stdio.h>
02 
03 int n;
04 int a[1000]; // 注意数组大小,待会要考。
05 
06 int f(int x) // 定义函数f,用来统计数字x在二进制中1的个数。(pop_count())
07 {
08         int ret = 0;
09         for(; x; x &= x - 1) ret ++;
10         return ret;
11 }
12 
13 int g(int x) // 定义函数g,取了二进制x中最低位的1的权重。(lowbit())
14 {
15         return x & - x;
16 }
17 
18 int main()
19 {
20         scanf("%d", &n);
21         for (int i = 0; i < n; i++) scanf("%d", &a[i]);
22         for (int i = 0; i < n; i++)
23             printf("%d ", f(a[i]) + g(a[i])); // 注意输出的形式,待会有用。
24         printf("\n");
25         return 0;
26 }
16

输入的 n 等于 1001 时,程序不会发生下标越界。(错误

因为数组大小为1000,所以下标为0 ~ 999,发生越界。

17

输入的 a[i] 必须全为正整数,否则程序将陷入死循环。(错误

f(x)在x为正整数或负整数时都能正常运行,故不会陷入死循环。

18

当输入为 5 2 11 9 16 10 时,输出为 3 4 3 17 5。(错误

最后输出为 f(a[i])+g(a[i])的形式。这个就需要我们手动模拟亿下了。
当输入5时,5的二进制是101:此时1的个数为2个,则 f(a[i])=2f(a[i])=2;
最低位的1表示为1,则g(a[i])=2g(a[i])=2 ;则输出为2+1=3.
以此类推,则:
2 → 2 + 1 正确
11 → 3 + 1 正确
9 → 2 + 1 正确
16 → 1 + 16 正确
10 → 2 + 2 错误
命题人太可恶了,最后一个输出给的是错误的(

19

当输入为 1 511998 时,输出为 18。(正确

511998 的二进制数为 0111 1100 1111 1111 1110
16 + 2 = 18

20

将源代码中 g 函数的定义(14∼17 行)移到 main 函数的后面,程序可以正常编译运行。(错误

函数定义需要在main()函数前面

21

当输入为 2 -65536 2147483647 时,输出为( )。
A. 65532 33
B. 65552 32
C. 65535 34
D. 65554 33

2147483647 怎么这么眼熟捏?没错,这是int的最大值,也就是0后面跟31个1
所以第二个输出就是31+1=32啦。

01 #include <iostream>
02 #include <string>
03 using namespace std;
04 // 恶补一下ASCII编码:A65 a97
115 - 97 = 18
05 char base[64];
06 char table[256];
07
08 void init() // 初始化base数组,提供一个编码表。
09 {
10		for (int i = 0; i < 26; i++) base[i] = 'A' + i;	// 0 ~ 25 A ~ Z
11		for (int i = 0; i < 26; i++) base[26 + i] = 'a' + i; // 26 ~ 51 a ~ z
12		for (int i = 0;i < 10;i++) base[52 + i]- '0' + i; // 52 ~ 61 0 ~ 9	
13		base[62] = '+',bat[63] = '/';	
14
15		for (int i = 0; i < 256; i++) table[i] = 0xff; // 0 ~ 25 0xff
16		for (int i = 0; i < 64;i++) table[base[i]] = i;	// 加密亿下。
17		table['='] = 0;	// '='对应'0',可以理解为占位符。
18}
19
20 string decode(string str) // 解密亿下。
21 {
22		string ret;	
23		int i;
24		for (i = 0; i < str.size(); i += 4) { // 4个字符经过for循环只剩下3个字符(!= '='的情况下)。
25			ret += table[str[i]] << 2 | table[str[i + 1]] >> 4;
26			if(str[í + 2] != '=')
27				ret += (table[str[i + 1]] & 0x0f) << 4 | table[str[i + 2]] >> 2;
28			if (str[i + 3] != '=')	
29				ret += table[str[1 + 2]] << 6 | table[str[1 + 3]];	
30		}
31		return ret;	
32 }
33
34 int main()
35 {
36		init();	// 初始化
37		cout << int(table[0]) << endl;	
38
39		string str;	
40		cin >> str;	
41		cout << decode(str) << endl;	
42		return 0;	
43 }
22

输出的第二行一定是由小写字母、大写字母、数字和 +、 /、= 构成的字符串。(错误

若输出的是'=',便不输出。

23

可能存在输入不同,但输出的第二行相同的情形。(正确

若输入分别为a0==a1==,则循环过后,第二行输出相同。

24

输出的第一行为 -1 。(正确

table数组初始化为0xff,观察程序,table[0]没有再被赋值过。
又因为0xff对应的二进制补码为 1111 1111,转化为十进制为 -1。

25

设输入字符串长度为 n,decode 函数的时间复杂度为( )。
A.
A
B.
B
C.
C
D.
D

最多单层for()循环,又考虑到遍历方法为转入整个数组,故时间复杂度为O(n)。

26

当输入为 Y3Nx 时,输出的第二行为( )。
A. csp
B. csq
C. CSP
D. Csp

带入模拟一下即可。
Y → 24 << 2 | 55 >> 4 → 0110 0011 → 99 → c
3 → 55 & 15 << 4 | 13 >> 2 → 0111 0011 → 115 → s
N → 13 << 6 | 23 → 0100 0000 | 0011 0001 → 0111 0001 → 113 → q

27

当输入为 Y2NmIDIwMjE= 时,输出的第二行为( )。
A. ccf2021
B. ccf2022
C. ccf 2021
D. ccf 2022

根据长度判断::输出字符串长度应该为8,排除A、B选项。
随后按上一题的方法模拟即可。

01 #include <iostream> 
02 using namespace std; 
03 // 本质上它是欧拉筛(线型筛)的逻辑。
04 const int n = 100000; 
05 const int N = n + 1; 
06 
07 int m; // 质数个数的计数器.
08 int a[N], b[N], c[N], d[N]; // c[i]是i的最小质因子的个数.d[i]是g[i]连乘积中最小质因子连乘积的部分.
09 int f[N], g[N]; // f[i]是i的约数的个数.g[i]是i的约数和.
10 
11 void init() 
12 { 
13 		f[1] = g[1] = 1; // 输入为1的对策,记住这行代码,待会要考.
14  	for (int i = 2; i <= n; i++) { // 遍历质数从2开始循环.
15  		if (!a[i]) { // 如果a[i] == 0,说明a[i]为质数.
16  			b[m++] = i; // 存入b[]数组,质数数量加1.
17  			c[i] = 1, f[i] = 2; // i的最小质因数是i,个数为1.i的约数的个数为2,分别是1,i.
18  			d[i] = 1, g[i] = i + 1; // 根据公式,i只有一个质因子i.
19         	} 
20  		for (int j = 0; j < m && b[j] * i <= n; j++) { // 从最小质数开始筛,无论它是质数或合数.
21  			int k = b[j]; // k是已知比i小的质数。循环的第二个条件,i是质数,乘比i小的质数,便是一个新的合数.
22  			a[i * k] = 1; // i是合数,乘比i小的质数,便又是一个新合数,随后标记并筛去.
23  			if (i % k == 0) { // k是i*k的最小质因子,i包括最小质因子k,c[i]是k的个数.
24  				c[i * k] = c[i] + 1; // 在c[i]的最小质因子k的个数基础上+1.
25  				f[i * k] = f[i] / c[i * k] * (c[i * k] + 1); // f[i*k]的最小质因子k比f[i]多1个.
26 					d[i * k] = d[i];
27  				g[i * k] = g[i] * k + d[i]; 
28  				break; // k被i整除,k是i*k的最小质数,因为重复,退出循环.
29  			} 
30  			else { // i不能整除k.
31  				c[i * k] = 1; // i不包括最小质因子k,i*k的最小质因子个数为1.
32  				f[i * k] = 2 * f[i]; // f[i*k]的质因子比f[i]多一个k,且个数为1,所以因数个数为f[i] * (1 + 1).
33  				d[i * k] = g[i]; 
34  				g[i * k] = g[i] * (k + 1); 
35  			} 
36     		} 
37     } 
38 }
39 
40 int main() 
41 { 
42  	init(); 
43 
44  	int x; 
45  	cin >> x; 
46  	cout << f[x] << ' ' << g[x] << endl; 
47  	return 0; 
48 }

假设输入的 x 是不超过 1000 的自然数,完成下面的判断题和单选题:

28

若输入不为 1,把第 13 行删去不会影响输出的结果。(正确

当输入不为1时,后续循环不受任何影响

29

第 25 行的 f[i] / c[i * k]可能存在无法整除而向下取整的情况。 (错误

不可能。c[i*k] = c[i]+1,f[i]是因子的个数,它的连乘积包括(1 + c[i])

30

在执行完 init() 后,f 数组不是单调递增的,但 g 数组是单调递增的。 (错误

g[i]是i的约数和,如果i是质数,则g的值会较小。
例:g[4] = 1 + 2 + 4 = 7,g[5] = 1 + 5 = 6

31

init 函数的时间复杂度为( )。
A.
A
B.
B
C.
C
D.
D

欧拉筛时间复杂度为O(n)

32

在执行完 init() 后,f[1],f[2],f[3]…f[100] 中有( )个等于 2。
A. 23
B. 24
C. 25
D. 26

约数个数为2,说明该数为质数。所以题目的意思就是100以内有多少个质数,答案为25个。

33

当输入为 1000 时,输出为( )。
A. 15 1340
B. 15 2340
C. 16 2340
D. 16 1340

1000 = 23 + 53
约数个数 = 幂+1的平方 = (3 + 1)2 = 16
约数和 = ​幂次对应等比数列和的累乘 = (1 + 21 + 22 + 23) * (1 + 51 + 52 + 5 3) = 2340

完善程序题

(Josephus 问题) 有 n 个人围成一个圈,依次标号 0 至 n−1。从 0 号开始,依次 0,1,0,1,… 交替报数,报到 1 的人会离开,直至圈中只剩下一个人。求最后剩下人的编号。

试补全模拟程序。

01 #include <iostream>
02
03 using namespace std;
04
05 const int MAXN = 1000000;
06 int F[MAXN];
07 // f[i] = 0 说明未出圈.
08 int main() {
09 		int n;
10 		cin >> n;
11 		int i = 0,p = 0,c = 0; // c代表出圈人数,i是下标,p是状态模拟.
12 		while () {
13			if (F[i] == 0) {
14				if() {
15					(F[i] = 1;
16;
17				}
18;
19			}
20;
21		}
22 		int ans = -1;
23 		for (i = 0;i < n;i++)
24			if (F[i] == 0)
25				ans = i;
26		cout << ans << endl;
27		return 0;
28 }
34

①处应填( )
A.i < n
B.c < n
C.i < n- 1
D.c < n-1

循环需筛掉n-1个人才能结束。

35

②处应填( )
A.i % 2 == 0
B.i % 2 == 1
C.p
D.!p

满足p == 1条件时被筛去。

36

③处应填( )
A. i++
B. i = (i + 1) % n
C. c++
D. p ^=1

如果出圈的话,出圈人数+1

37

④处应填( )
A.i++
B.i = (i + 1) % n
C.c++
D.p ^= 1

改变p的状态,用异或1(相同为0,不同为1)实现。

38

⑤处应填( )
A.i++
B.i = (i + 1) % n
C.c++
D.p ^= 1

无论是否被筛掉都需要进行的操作,就是让i周而复始的扫描

(矩形计数) 平面上有 n 个关键点,求有多少个四条边都和 x 轴或者 y 轴平行的矩形,满足四个顶点都是关键点。给出的关键点可能有重复,但完全重合的矩形只计一次。

01 #include <iostream>
02
03 struct point{ // 定义结构体point
04       int x, y, id;
05 };
06
07 bool equals(struct point a,struct point b){ // 判断相等,与id无关.
08       return a.x == b.x && a.y == b.y;
09 }
10
11 bool cmp(struct point a, struct point b){ // 设置排序为升序或降序.(选项都为升序)
12       return; 
13 }
14
15 void sort(struct point A[], int n){ // 冒泡排序
16       for(int i = 0; i < n; i++)
17           for(int j = 1; j < n; j++)
18               if(cmp(A[j], A[j - 1])){ // 若a[j]更小,交换(cmp函数).
19                   struct point t=A[j];
20                   A[j] = A[j - 1];
21                   A[j - 1] = t;
22               }
23 }
24
25 int unique(struct point A[],int n){ // 去重函数.
25       int t = 0; // 计数器
25       for(int i = 0; i < n; i++)
25           if() // 如果不等于已经找到的,存入A[t],A[i]与已存入的A[t-1]比,考虑第一个数.
25               A[t++] = A[i];
30       return t;
31 }
32
32 int binary_search(struct point A[], int n, int x, int y){ // 二分查找
32       struct point p; // 查找目标
32       p.x = x;
33       p.y = y;
32       p.id = n; // 令id最大化
32       int a = 0, b = n - 1; // a、b分别是区间的左右边界
32       while(a < b){ // A[]升序
40           int mid =; // 
41           if()
42               a = mid + 1;
43           else 
44               b = mid; 
45       }
46       return equals(A[a], p);
47 }
48
49 #define MAXN 1000
50 struct point A[MAXN];
51 
52 int main(){
53    int n;
54    cin >> n;
55    for(int i = 0; i < n; i++){
56        cin >> a[i].x >> a[i].y;
57        A[i].id = i;
58    }
59    sort(A,n); // 排序
60    n = unique(A, n); // 去重
61    int ans = 0;
62    for(int i = 0; i < n; i++)
63        for(int j = 0; j < n; j++)
64            if(&& binary_search(A, n, A[i].x, A[j].y) && binary_search(A, n, A[j].x, A[i].y)){
65                ans++;
66            }
67    cout << ans << endl;
68    return 0;
69 }
39

①处应填 ( )
A. a.x != b.x ? a.x < b.x : a.id < b.id
B. a.x != b.x ? a.x < b.x : a.y < b.y
C. equals(a, b) ? a.id < b.id : a.x < b.x
D. equals(a, b) ? a.id < b.id : (a.x != b.x ? a.x < b.x : a.y < b.y)

从右上往左下排序即可,选择(结构体)排序的思路。

40

②处应填 ( )
A. i == 0 || cmp(A[i], A[i - 1])
B. t == 0 || equals(A[i], A[t - 1])
C. i == 0 || !cmp(A[i], A[i - 1])
D. t == 0 || !equals(A[i], A[t - 1])

只有D选项符合去重函数的不等于的条件。

41

③处应填 ( )
A. b - (b - a) / 2 + 1
B. (a + b + 1) >> 1
C. (a + b) >> 1
D. a + (b - a + 1) / 2

二分基本代码的考察。 >> 1 表示 / 2。二分查找左边界,不用+1。

42

④处应填 ( )
A. !cmp(A[mid], p)
B. cmp(A[mid], p)
C. cmp(p, A[mid])
D. !cmp(p, A[mid])

A[mid]小于p的情况,故A[mid]在前

43

⑤处应填 ( )
A. A[i].x == A[j].x
B. A[i].id < A[j].id
C. A[i].x == A[j].x && A[i].id < A[j].id
D. A[i].x < A[j].x && A[i].y < A[j].y

避免重复计算,前面的点需要小于后面的点。

尾声

每天回家抽出30min写的
算是第一篇真正的解析罢
有什么不对的地方可以指出来
就这样 睡了睡了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值