【题目】

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 }

判断题:

  1. 当 1000>=d>=b 时,输出的序列是有序的( )
  2. 当输入“5 5 1”时,输出为“1 1 5 5 5”( )
  3. 假设数组 c 长度无限制,该程序所实现的算法的时间复杂度是 O(b)的( )
    单选题:
  4. 函数 int logic(int x,int y)的功能是( )
    A. 按位与
    B. 按位或
    C. 按位异或
    D. 以上都不是
  5. 当输入为“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 AB:在A中且在B中的元素构成的集合
  • A与B的并集 A ∪ B A\cup B AB:在A中或在B中的元素构成的集合
  • A与B的差集 A − B A-B AB:在A中但不在B中的元素构成的集合
  • B关于全集A的补集 A \ B A\backslash B A\B:B是A的子集,在A中但不在B中元素构成的集合
集合运算集合运算公式位运算
取交集 A ∩ B A\cap B ABa & b
取并集 A ∪ B A\cup B ABa | b
取差集 A − B A-B ABa & ~b
取B关于A的补集 A \ B A\backslash B A\Ba ^ b

当B不是A的子集时,a ^ b表示的是在A中但不在B中的元素,以及在B中但不在A中元素构成的集合,即 ( A − B ) ∪ ( B − A ) (A-B)\cup (B-A) (AB)(BA) ( A ∪ B ) − ( A ∩ B ) (A\cup B) - (A\cap B) (AB)(AB)
当A和B没有交集时,a ^ b表示的集合为 A ∪ B A\cup B AB
在这里插入图片描述

【解题思路】

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 YX,也就是在集合Y中但不在集合X中的元素构成的集合。
x ^ y表示在X中但不在Y中的元素,以及在Y中但不在X中元素构成的集合,也就是 ( X − Y ) ∪ ( Y − X ) (X-Y)\cup (Y-X) (XY)(YX)
(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) (XY)(YX)(YX)=(XY)(YX),而该集合也可以表示为 ( X ∪ Y ) − ( X ∩ Y ) (X\cup Y) - (X\cap Y) (XY)(XY)
x & y表示集合 X ∩ Y X\cap Y XY,该集合与 ( X − Y ) ∪ ( Y − X ) (X-Y)\cup (Y-X) (XY)(YX)没有交集,两个没有交集的集合进行按位或|,或者按位异或^运算的结果都是两集合的并集。
因此(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 (XY)((XY)(XY))=XY
所以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]=(ai)%(b+1)=(5∣i)%6,求出c数组

i01234
c[i]55115

选择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(bmin(logb,d))O(bmin(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
该问题也可以通过列真值表的方法得出结果

xylogic(x, y)
000
011
101
111
与按位或运算的真值表相同。

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 db,所以recursion函数对c数组进行了完整的升序快速排序,输出的第100个数也就是整个序列中的最大值。
c数组中的元素通过公式 c [ i ] = ( a ∣ i ) % ( b + 1 ) = ( 10 ∣ i ) % 101 c[i] = (a|i)\%(b+1)=(10|i)\%101 c[i]=(ai)%(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二进制
1001100100
991100011
981100010
971100001
961100000
951011111

因此 ( 10 ∣ i ) (10|i) (10∣i)的最大值为95,即 ( 10 ∣ i ) % 101 (10|i)\%101 (10∣i)%101,也就是c数组中的最大值为95,因此输出的第100个数为95,选C。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐