CSP-J 2022 入门级 第一轮 阅读程序(2) 第22-27题

【题目】

CSP-J 2022 入门级 第一轮 阅读程序(2) 第22-27题
阅读程序

01 #include <algorithm>
02 #include <iostream>
03 #include <limits>
04
05 using namespace std;
06
07 const int MAXN = 105; 
08 const int MAXK = 105;
09
10 int h[MAXN][MAXK];
11
12 int f(int n, int m)
13 {
14     if (m == 1) return n;
15     if (n == 0) return 0;
16
17     int ret = numeric_limits<int>::max();
18     for (int i = 1; i <= n; i++)
19         ret = min(ret, max(f(n - i, m), f(i - 1, m - 1)) + 1);
20     return ret;
21 }
22
23 int g(int n, int m)
24 {
25     for (int i = 1; i <= n; i++)
26         h[i][1] = i;
27     for (int j = 1; j <= m; j++)
28         h[0][j] = 0;
29
30     for (int i = 1; i <= n; i++) {
31         for (int j = 2; j <= m; j++) {
32             h[i][j] = numeric_limits<int>::max();
33             for (int k = 1; k <= i; k++)
34                 h[i][j] = min(
35                     h[i][j],
36                     max(h[i - k][j], h[k - 1][j - 1]) + 1);
37         }
38     }
39
40     return h[n][m];
41 }
42
43 int main()
44 {
45     int n, m;
46     cin >> n >> m;
47     cout << f(n, m) << endl << g(n, m) << endl;
48     return 0;
49 }

假设输入的 n 、 m 均是不超过 100 的正整数,完成下面的判断题和单选题:
判断题
22. 当输入为“ 7 3 ”时,第 19 行用来取最小值的 min 函数执行了 449 次。( )
23. 输出的两行整数总是相同的。( )
24. 当 m 为 1 时,输出的第一行总为 n 。( )
单选题
25. 算法 g(n,m) 最为准确的时间复杂度分析结果为( )。
A. O ( n 3 2 / 𝑚 ) O(n^{\frac{3}{2}}/𝑚) O(n23/m)
B. O ( n m ) O(nm) O(nm)
C. O ( n 2 m ) O(n^2m) O(n2m)
D. O ( n m 2 ) O(nm^2) O(nm2)
26. 当输入为“ 20 2 ”时,输出的第一行为( )。
A. “ 4 ”
B. “ 5 ”
C. “ 6 ”
D. “ 20 ”
27. (4 分) 当输入为“ 100 100 ”时,输出的第一行为( )。
A. “ 6 ”
B. “ 7 ”
C. “ 8 ”
D. “ 9 ”

【题目考点】

1. 递推/递归 动态规划
2. C++11 numeric_limits

引入<limits>头文件后可以使用

  1. numeric_limits<数据类型>::max() 返回该数据类型可以表示的最大值
  2. numeric_limits<数据类型>::min() 返回该数据类型可以表示的最小值
  3. numeric_limits<数据类型>::epsilon() 返回该数据类型可以区分的两个数值的最小差值(即如果两个数值的差值小于该值,计算机认为这两个数值相等)

【解题思路】

观察代码可知
f函数使用递归算法:

  • 递归关系:f(n,m)的值为:i从1循环到n, max(f(n-i, m), f(i-1, m-1))+1的最小值
  • 递归出口:m是1时f(n,m)值为n,n是0时f(n,m)值为0。

g函数使用了递推算法:

  • 初始状态:j是1时h[i][j]值为i,i是0时h[i][j]值为0。
  • 递推关系:h[i][j]的值为:k从1循环到i,max(h[i-k][j], h[k-1][j-1])+1的最小值

对比二者,如果把f函数中的n替换为i,m替换为j,i替换为k。递归出口对应递推初始状态,递归关系对应递推关系。递推和递归本就是可以相互转化的两种方法。可以看出二者是解决相同问题的不同方法。

22. 当输入为“ 7 3 ”时,第 19 行用来取最小值的 min 函数执行了 449 次。( )
答:错误。
观察f函数,对于一次调用f(n,m),for循环中内容就要执行n次,也就是说会调用min函数n次。统计所有的递归调用,将n加和即可。
记c(n,m)为f(n, m)运行时min函数的调用次数,为递归调用中min调用次数的加和,再加上f(n,m)中对min的n次调用。
用填表法,实际是递推的方式,从m,n值小的情况逐步推到值大的情况。

c(n,m)m=2m=3
n=1c(0,2)+c(0,1)+1=1c(0,3)+c(0,2)+1=1
n=2c(1,2)+c(0,2)+2=3c(1,3)+c(0,3)+c(0,2)+c(1,2)+2=4
n=3c(2,2)+c(1,2)+3=7c(2,3)+c(1,3)+c(1,2)+c(2,2)+3=12
n=4c(3,2)+c(2,2)+c(1,2)+4=15c(3,3)+c(2,3)+c(1,3)+c(1,2)+c(2,2)+c(3,2)+4=32
n=5c(4,2)+…+c(1,2)+5=31c(4,3)+…+c(1,3)+c(1,2)+…+c(4,2)+5=80
n=6c(5,2)+…+c(1,2)+6=63c(5,3)+…+c(1,3)+c(1,2)+…+c(5,2)+6=192
n=7c(6,2)+…+c(1,2)+7=127c(6,3)+…+c(1,3)+c(1,2)+…+c(6,2)+7=448

其实算几个后就能看出规律了,其中 c ( n , 2 ) = c ( n − 1 , 2 ) ∗ 2 + 1 , c ( n , 3 ) = 2 ∗ c ( n − 1 , 3 ) + c ( n − 1 , 2 ) + 1 c(n,2)=c(n-1,2)*2+1,c(n,3)=2*c(n-1,3)+c(n-1,2)+1 c(n,2)=c(n1,2)2+1c(n,3)=2c(n1,3)+c(n1,2)+1
得到运行f(7,3)时min的调用次数为448次,不是449次。

23. 输出的两行整数总是相同的。( )
答:正确。因为通过观察可知:f函数使用递归,g函数使用递推解决了相同的问题,结果是相同的。

24. 当 m 为 1 时,输出的第一行总为 n 。( )
答:正确。递归函数f中,如果m为1,会返回n。递推函数g中,当j为1时,h[i][j]为i。那么返回值h[n][m]一定为n。

25. 算法 g(n,m) 最为准确的时间复杂度分析结果为( )。
A. O ( n 3 2 / 𝑚 ) O(n^{\frac{3}{2}}/𝑚) O(n23/m)
B. O ( n m ) O(nm) O(nm)
C. O ( n 2 m ) O(n^2m) O(n2m)
D. O ( n m 2 ) O(nm^2) O(nm2)

答:选C
第25,26行循环n次,复杂度为O(n)
第27,28行循环m次,复杂度为O(m)
对于第30,31行,谁在内层谁在外层都不影响最内层代码执行的次数。
我们把for(int j = 2; j <= m; ++j)当做外层,这一层近似于循环m次,
对于第33行for(int k = 1; k <= i; ++k),i是1时循环1次,i是2时循环2次,。。。,i是n时循环n次,那么在j固定时,i从1循环到n,最内层循环体执行次数为: 1 + 2 + . . . + n = ( 1 + n ) n / 2 1+2+...+n=(1+n)n/2 1+2+...+n=(1+n)n/2
总执行次数为 m ⋅ n ( 1 + n ) / 2 m\cdot n(1+n)/2 mn(1+n)/2
整段代码的时间复杂度为: O ( n ) + O ( m ) + O ( m ⋅ n ( 1 + n ) / 2 ) = O ( n 2 m ) O(n)+O(m)+O(m\cdot n(1+n)/2)=O(n^2m) O(n)+O(m)+O(mn(1+n)/2)=O(n2m)
26. 当输入为“ 20 2 ”时,输出的第一行为( )。
A. “ 4 ”
B. “ 5 ”
C. “ 6 ”
D. “ 20 ”

答:选C
模拟运行递推算法g函数,填表表中第i行第j列为h[i][j]的值
j为1时,h[i][j]的值为1。
h[i][j]的值为:k从1循环到i,max(h[i-k][j], h[k-1][j-1])+1的最小值
算过几个后就可以找到规律:j=1这一列从i=0开始从上向下看,j=2这一列从i-1开始从下向上看,对应位置的数值求最大值,看哪一组求得的最大值最大,结果再加1,就是h[i][j]的值。
算出一些数字后,就能发现规律:1个1,2个2,3个3,…,6个6。

h[i][j]j=1j=2
i=000
i=111
i=222
i=332
i=443
i=553
i=663
i=774
i=884
i=994
i=10104
i=11115
i=12125
i=13135
i=14145
i=15155
i=16166
i=17176
i=18186
i=19196
i=20206

得到h[20][2]的值为6。

27. (4 分) 当输入为“ 100 100 ”时,输出的第一行为( )。
A. “ 6 ”
B. “ 7 ”
C. “ 8 ”
D. “ 9 ”

答:选B
尝试列出第三列,观察数值的变化规律

h[i][j]j=1j=2j=3
i=0000
i=1111
i=2222
i=3322
i=4433
i=5533
i=6633
i=7743
i=8844
i=9944
i=101044

举例:在填h[4][3]时,根据规则,j=2这一列从i=0开始向下看,j=3这一列从i=2开始向上看,先比较h[0][2]和h[3][3],再分别比较h[1][2]与h[2][3],h[2][2]与h[1][3],h[3][2]与h[0][3],最大值都是2,加1后是3。所以h[4][3]填3。
接下来用相同的方法填h[5][3], h[6][3],h[7][3]都是3。
填h[8][3]时就是4了,思考为什么数值增大了呢?正是因为当填h[8][3]时,j=2这一列i从0到4值是0,1,2,2。j=3这一列i从7到4的值为3,3,3,3,最大值都为3,加1后是4。
设想j=4时,i从0到7,h[i][4]的值与h[i][3]的值都相同,那么从h[8][4]开始,随着i的增大,需要填一些4,那么什么时候应该填5呢,需要让每个数对的最大值都是4,前面h[0][3]到h[7][3]是0,1,2,2,3,3,3,3,共8个数字,需要与后面8个数字4配对,这8个数字的位置应该为h[15][4]到h[8][4],这样最大值都是4,接下来h[8][3]往下都是4,与j=4的一列较小值配对,最大值都是4。
因此第一个填5的位置应该是h[16][4],在它前面一共有8个4。
j是2时确定了有2个2,j是3时确定了有4个3,j是4时就能确定有8个4,…,在j=100时,各项数值一定都已经进行了充分的计算,计算后,一列之中应该有1个0,1个1,2个2,4个3,8个4,16个5,32个6,64个7,。。。
列举出每个数字第一次出现的位置
h[1][100]=1
h[2][100]=2
h[4][100]=3
h[8][100]=4
h[16][100]=5
h[32][100]=6
h[64][100]=7
h[128][100]=8
因此h[100][100]的值为7。

【其他解法】

本题的原题实质是信息学奥赛一本通 1300:鸡蛋的硬度 | OpenJudge NOI 2.6 7627:鸡蛋的硬度,除非你做过这一问题,能通过看代码得知这是个扔鸡蛋问题,否则很难在考场上就能从扔鸡蛋的角度来思考这一问题。
该题求的是最坏情况下最少扔鸡蛋的次数。
在了解这个代码是扔鸡蛋问题后,第26题解法为:
只有2个鸡蛋,设最坏情况下扔鸡蛋测出楼层高度最少需要扔x次
第一次最高也只能在第x层,如果在更高层扔,如果鸡蛋碎了,那么剩下1个鸡蛋在扔x-1次未必能测出鸡蛋硬度。
在第x层扔,如果碎了,那么剩下一个鸡蛋从第1层开始扔,如果没碎,就在第2层扔,不断升高楼层,当鸡蛋在第i层碎了时,鸡蛋硬度为i-1。
第一次在第x层扔鸡蛋如果没碎,那么剩下x-1次机会,楼层数是20-x,确定鸡蛋的硬度。
接下来在第x+x-1层扔鸡蛋,如果没碎,那么剩下x-2次机会,楼层数是20-x-(x-1),确定鸡蛋的硬度。
为了确定鸡蛋在20层楼内的硬度,需要x+(x-1)+(x-2)+…+1>=20
即(1+x)x/2 >= 20
x是整数,可以取到的最小值为6。

第27题,100层楼有100个鸡蛋,确定鸡蛋硬度,鸡蛋足够了,就可以用二分查找的方法确定鸡蛋的硬度,二分查找最大比较次数为 ⌊ l o g 2 100 ⌋ + 1 = 7 \lfloor log_2100\rfloor+1=7 log2100+1=7

【答案】

  1. 错误
  2. 正确
  3. 正确
  4. C
  5. C
  6. B
  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值