当前位置: 首页 > article >正文

CSP-S 2024 提高级 第一轮(初赛) 阅读程序(2)

【题目】

CSP-S 2024 提高级 第一轮(初赛) 阅读程序(2)

1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 const int P = 998244353, N = 1e4 + 10, M = 20;
6 int n, m;
7 string s;
8 int dp[1 << M];
9
10 int solve() {
11     dp[0] = 1;
12     for (int i = 0; i < n; i++) {
13         for (int j = (1 << (m - 1)) - 1; j >= 0; j--) {
14             int k = (j << 1) | (s[i] - '0');
15             if (j != 0 || s[i] == '1')
16                 dp[k] = (dp[k] + dp[j]) % P;
17         }
18     }
19     int ans = 0;
20     for (int i = 0; i < (1 << m); i++) {
21         ans = (ans + 1ll * i * dp[i]) % P;
22     }
23     return ans;
24 }
25 int solve2() {
26     int ans = 0;
27     for (int i = 0; i < (1 << n); i++) {
28         int cnt = 0;
29         int num = 0;
30         for (int j = 0; j < n; j++) {
31             if (i & (1 << j)) {
32                 num = num * 2 + (s[j] - '0');
33                 cnt++;
34             }
35         }
36         if (cnt <= m)(ans += num) %= P;
37     }
38     return ans;
39 }
40
41 int main() {
42     cin >> n >> m;
43     cin >> s;
44     if (n <= 20) {
45         cout << solve2() << endl;
46     }
47     cout << solve() << endl;
48     return 0;
49 }

假设输入的 s 是包含 n 个字符的 01 串,完成下面的判断题和单选题:
判断题
1. 函数 solve()所实现的算法时间复杂度是 O ( n ∗ 2 m ) O(n*2^m) O(n2m)
2. 输入“11 2 10000000001”时,程序输出两个数 32 和 23。
3. 在 n<=10 时,solve()的返回值始终小于 4 10 4^{10} 410
单选题
4. 当 n=10 且 m=10 时,有多少种输入使得两行的结果完全一致?
A.1024
B.11
C.10
D.0

5. 当 n<=6 时,solve()的最大可能返回值为?
A.65
B.211
C.665
D.2059

6. 若 n=8,m=8,solve 和 solve2 的返回值的最大可能的差值为?
A.1477
B.1995
C.2059
D.2187

【题目考点】

1. 状压动规

【解题思路】

41 int main() {
42     cin >> n >> m;
43     cin >> s;
44     if (n <= 20) {
45         cout << solve2() << endl;
46     }
47     cout << solve() << endl;
48     return 0;
49 }

先看主函数,n,m是整数,s是一个字符串。
n ≤ 20 n\le 20 n20时,调用solve2函数

25 int solve2() {
26     int ans = 0;
27     for (int i = 0; i < (1 << n); i++) {
28         int cnt = 0;
29         int num = 0;
30         for (int j = 0; j < n; j++) {
31             if (i & (1 << j)) {
32                 num = num * 2 + (s[j] - '0');
33                 cnt++;
34             }
35         }
36         if (cnt <= m)(ans += num) %= P;
37     }
38     return ans;
39 }

i从0循环到(1<<n)-1,i是状压后的二进制集合状态,枚举所有状态,从n位0循环到n位1。
循环内cnt和num初值为0。
j从0循环到n-1,判断i&(1<<j),就是判断二进制下i的第j位(从0开始数)是否为1。
num*2+(s[j]-'0')等价于num<<1 | (s[j]-'0')
如果i的第j位为1,那么将num在最低位添加数字s[j]
j循环结束后,num的值为:从低位到高位遍历,i中有1的位置,就是在s中选择的数字的位置,选出的数字形成二进制数字,就是num。
例:

位置3210
s0110
i1110

该例中num为 ( 110 ) 2 (110)_2 (110)2
i指明了在s中选择数字的位置,也就是表示了s中的一个子序列,num为由这个子序列构造出来的数字。
cnt统计的是二进制数i中有多少位是1,如果i中1的位数不超过m,也就是选出的子序列长度不超过m,那么ans增加构造好的num数字,结果模P。
ans为s中所有长度不超过m的子序列构成的数字加和模P。

10 int solve() {
11     dp[0] = 1;
12     for (int i = 0; i < n; i++) {
13         for (int j = (1 << (m - 1)) - 1; j >= 0; j--) {
14             int k = (j << 1) | (s[i] - '0');
15             if (j != 0 || s[i] == '1')
16                 dp[k] = (dp[k] + dp[j]) % P;
17         }
18     }
19     int ans = 0;
20     for (int i = 0; i < (1 << m); i++) {
21         ans = (ans + 1ll * i * dp[i]) % P;
22     }
23     return ans;
24 }

再看solve函数,调用solve函数时,n可能大于20。
i从0~n-1循环,循环中用到了s[i],可见i是字符串s的下标,因此n的意义应该是字符串s的长度。
j从 2 m − 1 − 1 2^{m-1}-1 2m11遍历到0,很明显j是二进制集合状态。
根据第20行i从0到 2 m − 1 2^m-1 2m1循环,可知集合状态应该有m位。那么j从 2 m − 1 − 1 2^{m-1}-1 2m11遍历到0,就是在枚举最高位也就是第m-1位为0的所有状态。
集合状态k为:j末尾添加数字s[i]。k比j多一位,因此k是m位二进制数字。
一会儿再研究if判断的意义
状态转移方程为dp[k] = (dp[k]+dp[j])%P
dp[k]在模P意义下增加了dp[j],结合dp[0]=1,dp[k]的意义应该是求集合状态为k的方案数。问题是dp的下标,也就是集合状态表示了什么?

对于j的某个值jm,记k的值km = (jm<<1)|1,如果s[i1], s[i2], s[i3]…都为1,
那么当i为i1时,s[i1]为’1’,j循环到jm时,dp[km]增加dp[jm]
当i为i2时,s[i2]为’1’,j循环到jm时,dp[km]增加dp[jm]
当i为i3时,s[i3]为’1’,j循环到jm时,dp[km]增加dp[jm]

i是s的下标,也就是通过下标i选择s中的数字。选出的数字添加到了已有集合状态j的末尾,形成了新的集合状态k。可以在s中选择不同位置的数字,构造出相同的集合状态。
因此,集合状态k为从s中选出的一些数字,也就是选出一个子序列,前后相接构造成的二进制数字。
dp[k]的意义是从字符串s中选出子序列构造出的二进制数字为k的方案数量。
再看判断if(j != 0 || s[i] == '1')
如果j == 0 && s[i] == '0',那么k为0,dp[0]初值为1。而后如果s[i]为’0’,k为j末尾添加0后的值,还是0。由于"0", “00”, “000”,构造出的数字都是0,dp[0]没有办法表示多种选择子序列的方案数,因此dp[0]只能表示空串的方案数,没有表示选出多个0构成的子序列的方案数。
如果k为0,状态转移方程也不会执行,因此有判断if(j != 0 || s[i] == '1')。这会导致dp[k]所表示的子序列的方案数中,不包含有前导0的子序列。

例:如j为0,s[i]为’1’,那么k = (j<<1)|1=1,dp[k] = dp[k]+dp[j] = dp[1]+dp[0] = 1。这一种方案只有选出的子序列为1的情况,不包含01,001,0001等情况,因为dp[0]中没有包含0, 00, 000这些子序列的情况数。

最后集合状态i从0到 2 m − 1 2^m-1 2m1循环,枚举m位二进制数字,也就是枚举所有子序列构造出来的数字,每次让ans增加i*dp[i],也就是dp[i]个i相加。也可以理解为,每选出一个子序列构造出数字i,就把一个数值i加到ans上。
因此ans为s中所有长度不超过m的没有前导0的子序列构成的数字加和模P。

判断题
1. 函数 solve()所实现的算法时间复杂度是 O ( n ∗ 2 m ) O(n*2^m) O(n2m)
答:T

12     for (int i = 0; i < n; i++) {
13         for (int j = (1 << (m - 1)) - 1; j >= 0; j--) {

外层i循环n次,内层j循环 2 m − 1 2^{m-1} 2m1次,总体时间复杂度为 O ( n ∗ 2 m ) O(n*2^m) O(n2m)

2. 输入“11 2 10000000001”时,程序输出两个数 32 和 23。
答:T
n=11, m=2, s=“10000000001”
找长度不超过2的子序列
由于是做加和,不用考虑所有数字都是0的子序列。

子序列数值个数
112
0119
1029
1131

solve2为长度不超过2的子序列构造数字加和: 1 ∗ 2 + 1 ∗ 9 + 2 ∗ 9 + 3 ∗ 1 = 32 1*2+1*9+2*9+3*1=32 12+19+29+31=32
solve为长度不超过2的无前导0子序列构造的数字加和: 1 ∗ 2 + 2 ∗ 9 + 3 ∗ 1 = 23 1*2+2*9+3*1=23 12+29+31=23

n ≤ 20 n\le 20 n20
3. 在 n<=10 时,solve()的返回值始终小于 4 10 4^{10} 410
答:T
考虑solve的取最大值时,n取最大值为10,s最大时有10位1,即1111111111。

子序列数值个数
1 2 − 1 2-1 21C(9,1)
11 2 2 − 1 2^2-1 221C(9,2)
111 2 3 − 1 2^3-1 231C(9,3)
1111111111 2 10 − 1 2^{10}-1 2101C(9,9)

结果为 ( 2 − 1 ) ∗ C ( 9 , 1 ) + ( 2 2 − 1 ) ∗ C ( 9 , 2 ) + . . . + ( 2 10 − 1 ) ∗ C ( 9 , 9 ) < 2 10 ( C ( 9 , 0 ) + C ( 9 , 1 ) + . . . + C ( 9 , 9 ) ) = 2 10 ∗ 2 9 = 2 19 < 2 20 = 4 10 (2-1)*C(9,1)+(2^2-1)*C(9,2)+...+(2^{10}-1)*C(9,9) < 2^{10}(C(9,0)+C(9,1)+...+C(9,9)) = 2^{10}*2^9=2^{19}<2^{20}=4^{10} (21)C(9,1)+(221)C(9,2)+...+(2101)C(9,9)<210(C(9,0)+C(9,1)+...+C(9,9))=21029=219<220=410
(根据二项式定理,有 2 n = C ( n , 0 ) + . . . + C ( n , n ) 2^n=C(n,0)+...+C(n,n) 2n=C(n,0)+...+C(n,n)

单选题
4. 当 n=10 且 m=10 时,有多少种输入使得两行的结果完全一致?
A.1024
B.11
C.10
D.0

答:B
若想使solve和solve2的结果一致,则s中有前导0的子序列必须每位都为0,那么字符串的形式必须是由全是1构成的子串和全是0构成的子串连接而成。
字符串长度为10,中1个个数最少是0个,最多是10个,共有11种情况。

5. 当 n<=6 时,solve()的最大可能返回值为?
A.65
B.211
C.665
D.2059

答:
显然,当n=6,且s是6位1,也就是111111时,结果最大。

子序列数值个数
11C(6,1)=6
113C(6,2)=15
1117C(6,3)=20
111115C(6,4)=15
1111131C(6,5)=6
11111163C(6,6)=1

结果为: 1 ∗ 6 + 3 ∗ 15 + 7 ∗ 20 + 15 ∗ 15 + 31 ∗ 6 + 63 ∗ 1 = 665 1*6+3*15+7*20+15*15+31*6+63*1 =665 16+315+720+1515+316+631=665

6. 若 n=8,m=8,solve 和 solve2 的返回值的最大可能的差值为?
A.1477
B.1995
C.2059
D.2187

答:C
两函数返回值的差值最大时,有前导0的子序列最多。
在0和1数量已经确定的情况下,应该让字符串s由全为0的子段和全为1的子段连接而成。
01111111 是差值最大的方案,这种情况下,包含前导零的方案正好对应一
个不包含的方案
C ( 7 , 1 ) ∗ 1 + C ( 7 , 2 ) ∗ 3 + C ( 7 , 3 ) ∗ 7 + C ( 7 , 4 ) ∗ 15 + C ( 7 , 5 ) ∗ 31 + C ( 7 , 6 ) ∗ 63 + C ( 7 , 7 ) ∗ 127 = 2059 C(7,1)*1+C(7,2)*3+C(7,3)*7+C(7,4)*15+C(7,5)*31+C(7,6)*63+C(7,7)*127=2059 C(7,1)1+C(7,2)3+C(7,3)7+C(7,4)15+C(7,5)31+C(7,6)63+C(7,7)127=2059


http://www.kler.cn/a/318644.html

相关文章:

  • Java结合ElasticSearch根据查询关键字,高亮显示全文数据。
  • 封装一个省市区的筛选组件
  • 基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
  • C++ 的协程
  • 山泽光纤HDMI线:铜线的隐藏力量
  • 结构体是否包含特定类型的成员变量
  • 【OSS安全最佳实践】降低因操作失误等原因导致数据丢失的风险
  • 【C++笔试强训】如何成为算法糕手Day2
  • 【c++】知识点
  • 分布式光伏监控系统 在鄂尔多斯市鄂托克旗某煤矿项目中的应用
  • GPU高性能编程CUDA入门
  • 拦截器filter
  • 【ShuQiHere】 探索自然语言处理的世界:从基础到应用
  • flutter中常见的跨组件通讯方式
  • Redis 分布式缓存服务(集群)
  • str函数的模拟(包括strn函数的模拟)
  • 江科大51单片机
  • 2024年前端框架选择指南:React、Vue、Angular与新兴框架对比
  • 详解机器学习经典模型(原理及应用)——支持向量机
  • 每天一个数据分析题(四百七十二)- 业务角度
  • 使用nc命令检测UDP端口
  • Android13中Android.mk和Android.bp预编译多种架构文件
  • spark初步探索
  • LD3320语音识别模块的简单应用
  • 从 HDFS 迁移到 MinIO 企业对象存储
  • thinkphp6.0 伪静态失效404(win下)