题解目录
写在前面
作者马上要考CSP-J了,刷点历年真题,
特出此篇,帮助自己各位。
加粗字体代表答案,标记
字体代表重点。
(作者近期非常高产)
题面
很抱歉,本博客仅仅提供讲解,
有需要的同学可以前往刷题网站自主练习。
洛谷有题
题解
答案合集(表格)
题号 | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ |
---|---|---|---|---|---|
1~5 | D | B | A | C | D |
6~10 | D | C | A | B | B |
11~15 | B | A | C | B | B |
16~20 | 错误 | 错误 | 错误 | 正确 | 错误 |
21~25 | B | 错误 | 正确 | 正确 | B |
26~30 | B | C | 正确 | 错误 | 错误 |
31~35 | A | C | C | D | C |
36~40 | C | D | B | B | D |
41~45 | C | B | D | – | – |
单项选择题
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 四个点中有可能作为最后一个遍历到的点的个数为( )。
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.
B.
C.
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.
B.
C.
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写的
算是第一篇真正的解析罢
有什么不对的地方可以指出来
就这样 睡了睡了