CSP-S 2024 提高级 第一轮(初赛) 阅读程序(1)
前面的下标i选择大于等于pivot的元素,后面的下标j选择小于等于pivot的元素,二者交换。选择c[0]为标杆元素,从前向后找第一个大于等于c[0]的元素是c[0],从后向前找第一个小于等于c[0]的元素为c[4],二者交换,由于二者的数值都为5,所以c数组从数值角度来看没有变化。当B不是A的子集时,a ^ b表示的是在A中但不在B中的元素,以及在B中但不在A中元素构成的集合,即。表示在X中但不
【题目】
CSP-S 2024 提高级 第一轮(初赛) 阅读程序(1)
1 #include <iostream>
2 using namespace std;
3
4 const int N = 1000;
5 int c[N];
6
7 int logic(int x, int y) {
8 return (x & y) ^ ((x ^ y) | (~x & y));
9 }
10 void generate(int a, int b, int *c) {
11 for (int i = 0; i < b; i++) {
12 c[i] = logic(a, i) % (b + 1);
13 }
14 }
15 void recursion(int depth, int *arr, int size) {
16 if (depth <= 0 || size <= 1) return;
17 int pivot = arr[0];
18 int i = 0, j = size - 1;
19 while (i <= j) {
20 while (arr[i] < pivot) i++;
21 while (arr[j] > pivot) j--;
22 if (i <= j) {
23 int temp = arr[i];
24 arr[i] = arr[j];
25 arr[j] = temp;
26 i++;j--;
27 }
28 }
29 recursion(depth - 1, arr, j + 1);
30 recursion(depth - 1, arr + i, size - i);
31 }
32
33 int main() {
34 int a, b, d;
35 cin >> a >> b >> d;
36 generate(a, b, c);
37 recursion(d, c, b);
38 for (int i = 0; i < b; i++) cout << c[i] << " ";
39 cout<<endl;
40 }
判断题:
- 当 1000>=d>=b 时,输出的序列是有序的( )
- 当输入“5 5 1”时,输出为“1 1 5 5 5”( )
- 假设数组 c 长度无限制,该程序所实现的算法的时间复杂度是 O(b)的( )
单选题: - 函数 int logic(int x,int y)的功能是( )
A. 按位与
B. 按位或
C. 按位异或
D. 以上都不是 - 当输入为“10 100 100”时,输出的第 100 个数是( )
A.91
B.94
C.95
D.98
【题目考点】
1. 状态压缩
一个二进制数表示一个集合,一个x位二进制数从低位到高位分别为:第0位,第1位,…,第x-1位。
第i位为0表示集合中不存在第i元素。
第i位为1表示集合中存在第i元素。
集合A、B分别由二进制数a、b表示,那么集合运算可以通过二进制数位运算来完成
集合运算包括:
- A与B的交集 A ∩ B A\cap B A∩B:在A中且在B中的元素构成的集合
- A与B的并集 A ∪ B A\cup B A∪B:在A中或在B中的元素构成的集合
- A与B的差集 A − B A-B A−B:在A中但不在B中的元素构成的集合
- B关于全集A的补集 A \ B A\backslash B A\B:B是A的子集,在A中但不在B中元素构成的集合
集合运算 | 集合运算公式 | 位运算 |
---|---|---|
取交集 | A ∩ B A\cap B A∩B | a & b |
取并集 | A ∪ B A\cup B A∪B | a | b |
取差集 | A − B A-B A−B | a & ~b |
取B关于A的补集 | A \ B A\backslash B A\B | a ^ b |
当B不是A的子集时,a ^ b表示的是在A中但不在B中的元素,以及在B中但不在A中元素构成的集合,即
(
A
−
B
)
∪
(
B
−
A
)
(A-B)\cup (B-A)
(A−B)∪(B−A)或
(
A
∪
B
)
−
(
A
∩
B
)
(A\cup B) - (A\cap B)
(A∪B)−(A∩B)
当A和B没有交集时,a ^ b表示的集合为
A
∪
B
A\cup B
A∪B
【解题思路】
7 int logic(int x, int y) {
8 return (x & y) ^ ((x ^ y) | (~x & y));
9 }
先看logic函数,如果仅仅从位运算角度来看,会感觉该运算十分复杂。但如果学过状态压缩,把x、y两个二进制数字当做集合X、Y的集合状态,那么~x & y
就是Y与X的差集
Y
−
X
Y-X
Y−X,也就是在集合Y中但不在集合X中的元素构成的集合。
x ^ y
表示在X中但不在Y中的元素,以及在Y中但不在X中元素构成的集合,也就是
(
X
−
Y
)
∪
(
Y
−
X
)
(X-Y)\cup (Y-X)
(X−Y)∪(Y−X)。
(x ^ y) | (~x & y)
就是求二者的并集
(
X
−
Y
)
∪
(
Y
−
X
)
∪
(
Y
−
X
)
=
(
X
−
Y
)
∪
(
Y
−
X
)
(X-Y)\cup (Y-X) \cup (Y-X)=(X-Y)\cup (Y-X)
(X−Y)∪(Y−X)∪(Y−X)=(X−Y)∪(Y−X),而该集合也可以表示为
(
X
∪
Y
)
−
(
X
∩
Y
)
(X\cup Y) - (X\cap Y)
(X∪Y)−(X∩Y)
x & y
表示集合
X
∩
Y
X\cap Y
X∩Y,该集合与
(
X
−
Y
)
∪
(
Y
−
X
)
(X-Y)\cup (Y-X)
(X−Y)∪(Y−X)没有交集,两个没有交集的集合进行按位或|
,或者按位异或^
运算的结果都是两集合的并集。
因此(x & y) ^ ((x ^ y) | (~x & y))
为
(
X
∩
Y
)
∪
(
(
X
∪
Y
)
−
(
X
∩
Y
)
)
=
X
∪
Y
(X\cap Y)\cup ((X\cup Y) - (X\cap Y))=X\cup Y
(X∩Y)∪((X∪Y)−(X∩Y))=X∪Y
所以logic函数的返回值就是x | y
。
10 void generate(int a, int b, int *c) {
11 for (int i = 0; i < b; i++) {
12 c[i] = logic(a, i) % (b + 1);
13 }
14 }
generate函数就是对c数组进行赋值,使c[i]
的值为(a|i)%(b+1)
15 void recursion(int depth, int *arr, int size) {
16 if (depth <= 0 || size <= 1) return;
17 int pivot = arr[0];
18 int i = 0, j = size - 1;
19 while (i <= j) {
20 while (arr[i] < pivot) i++;
21 while (arr[j] > pivot) j--;
22 if (i <= j) {
23 int temp = arr[i];
24 arr[i] = arr[j];
25 arr[j] = temp;
26 i++;j--;
27 }
28 }
29 recursion(depth - 1, arr, j + 1);
30 recursion(depth - 1, arr + i, size - i);
31 }
应该能看出,这是在做二路快速排序。pivot是标杆元素,选择第1个元素作为标杆元素。前面的下标i选择大于等于pivot的元素,后面的下标j选择小于等于pivot的元素,二者交换。交换后前面的元素较小,后面的元素较大,因此该函数进行的是升序排序。
而这里增加了depth参数,当depth为0时,不再进行递归。depth是最大的递归深度,也就是说只进行最大递归深度为depth的快速排序。
33 int main() {
34 int a, b, d;
35 cin >> a >> b >> d;
36 generate(a, b, c);
37 recursion(d, c, b);
38 for (int i = 0; i < b; i++) cout << c[i] << " ";
39 cout<<endl;
40 }
主函数中,先输入a、b、d。
使用a、b通过generate函数生成数组c,下标从0~b-1。
对数组c进行最大递归层数为d的快速排序,而后输出数组c。
【试题答案及解析】
判断题:
1.当 1000>=d>=b 时,输出的序列是有序的( )
答:T
b是c数组元素个数,c数组长度为N是1000,因此b的值最大可以达到1000。d是快速排序的递归层数,最坏情况下,要想完成排序,递归层数会等于元素个数。当递归层数d大于等于元素个数b时,一定可以完成对数组c的排序。
2. 当输入“5 5 1”时,输出为“1 1 5 5 5”( )
答:F
已知a=5,b=5,d=1。
根据
c
[
i
]
=
(
a
∣
i
)
%
(
b
+
1
)
=
(
5
∣
i
)
%
6
c[i] = (a|i)\%(b+1)=(5|i)\%6
c[i]=(a∣i)%(b+1)=(5∣i)%6,求出c数组
i | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
c[i] | 5 | 5 | 1 | 1 | 5 |
选择c[0]为标杆元素,从前向后找第一个大于等于c[0]的元素是c[0],从后向前找第一个小于等于c[0]的元素为c[4],二者交换,由于二者的数值都为5,所以c数组从数值角度来看没有变化。
由于递归层数d为1,因此只进行一趟选择和交换。输出应该为5 5 1 1 5。
3. 假设数组 c 长度无限制,该程序所实现的算法的时间复杂度是 O(b)的( )
答:F
generate函数和输出过程的复杂度都为
O
(
b
)
O(b)
O(b)
对有b个元素的序列进行快速排序,递归层数最好情况下是
O
(
l
o
g
b
)
O(logb)
O(logb),最坏情况下是
O
(
b
)
O(b)
O(b),由于该问题递归层数受输入的d的限制,因此递归层数为
O
(
m
i
n
(
l
o
g
b
,
d
)
)
∼
O
(
m
i
n
(
b
,
d
)
)
O(min(logb, d))\sim O(min(b, d))
O(min(logb,d))∼O(min(b,d))。每一层递归所遍历的元素平均有b个。因此recursion函数的时间复杂度为
O
(
b
∗
m
i
n
(
l
o
g
b
,
d
)
)
∼
O
(
b
∗
m
i
n
(
b
,
d
)
)
O(b*min(logb, d))\sim O(b*min(b, d))
O(b∗min(logb,d))∼O(b∗min(b,d))
该程序所实现的算法总体时间复杂度最好为
O
(
b
log
b
)
O(b\log b)
O(blogb),最坏为
O
(
b
2
)
O(b^2)
O(b2),该叙述错误。
单选题:
4. 函数 int logic(int x,int y)的功能是( )
A. 按位与
B. 按位或
C. 按位异或
D. 以上都不是
答:B
根据上述分析,可知logic实现的是x | y
。
该问题也可以通过列真值表的方法得出结果
x | y | logic(x, y) |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
与按位或运算的真值表相同。 |
5. 当输入为“10 100 100”时,输出的第 100 个数是( )
A.91
B.94
C.95
D.98
答:C
a = 10, b = 100, d = 100
由于
d
≥
b
d\ge b
d≥b,所以recursion函数对c数组进行了完整的升序快速排序,输出的第100个数也就是整个序列中的最大值。
c数组中的元素通过公式
c
[
i
]
=
(
a
∣
i
)
%
(
b
+
1
)
=
(
10
∣
i
)
%
101
c[i] = (a|i)\%(b+1)=(10|i)\%101
c[i]=(a∣i)%(b+1)=(10∣i)%101生成,i是从0到99的整数。
将10在2进制下按位权展开:
10
=
2
+
2
3
10 = 2+2^3
10=2+23,转为二进制数后为
(
10
)
10
=
(
1010
)
2
(10)_{10}=(1010)_2
(10)10=(1010)2
由于c[i]是一个数模101的结果,也就是说最终结果不能超过100。
现在需要找的是一个小于等于
(
100
)
10
(100)_{10}
(100)10的,二进制下第1位和第3位必须为1的最大的数字。
从大到小枚举
(
10
∣
i
)
(10|i)
(10∣i)的值,看其第1位和第3位是否都为1
10|i十进制 | 10|i二进制 |
---|---|
100 | 1100100 |
99 | 1100011 |
98 | 1100010 |
97 | 1100001 |
96 | 1100000 |
95 | 1011111 |
因此 ( 10 ∣ i ) (10|i) (10∣i)的最大值为95,即 ( 10 ∣ i ) % 101 (10|i)\%101 (10∣i)%101,也就是c数组中的最大值为95,因此输出的第100个数为95,选C。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)