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

CSP-J 2021 入门级 第一轮(初赛) 阅读程序(1)

【题目】

CSP-J 2021 入门级 第一轮(初赛) 阅读程序(1)

01 #include <iostream>
02 using namespace std;
03
04 int n;
05 int a[1000];
06
07 int f(int x)
08 {
09     int ret = 0;
10     for (; x; x &= x-1) ret++;
11     return ret;
12 }
13
14 int g(int x)
15 {
16     return x & -x;
17 }
18
19 int main()
20 {
21     cin >> n;
22     for (int i = 0; i < n; i++) cin >> a[i];
23     for (int i = 0; i < n; i++)
24         cout << f(a[i]) + g(a[i]) << ' ';
25     cout << endl;
26     return 0;
27 }
  • 判断题:
  1. 输入的n等于1001时,程序不会发生下标越界。( )
  2. 输入的a[i]必须全为正整数,否则程序将陷入死循环。( )
  3. 当输入为 5 2 11 9 16 10 时,输出为 3 4 3 17 5。( )
  4. 当输入为 1 511998 时,输出为 18。( )
  5. 将源代码中 g 函数的定义(14∼17 行)移到 main 函数的后面,程序可以正常编译运行。( )
  • 单选题:
  1. 当输入为 2 -65536 2147483647 时,输出为( )。
    A. 65532 33
    B. 65552 32
    C. 65535 34
    D. 65554 33

【题目考点】

1. 位运算
2. 机器数

【解题思路】

先看f函数

07 int f(int x)
08 {
09     int ret = 0;
10     for (; x; x &= x-1) ret++;
11     return ret;
12 }

第10行的意义为:只要x不为0,就进行循环每次循环让x变为x & x-1的值。
已知内存中以补码形式保存数值。
-1的原码为:10...01
-1的反码为:11...10(除了符号位,各位取反)
-1的补码为:11...11(反码加1)

对一个数的补码减1与加-1得到的结果相同。

先考虑x不为0的情况。
设x的补码为
PD...(x位)...D10...(y位)...0
首先P是符号位,而后有x位,每位可能相同也可能不同,暂且都用D表示(不代表标记为D的这些位都相同)。由于x不为1,那么其中必然存在某一位为1。该数最后一位1后面有y位0(y可能为0)。
现在进行计算:x-1,也就是x加上-1。-1的补码每一位都是1,也就是要将x的每一位都增加1。
原数最后一位1后面的y位0变为y位1。
原数最后一位1加1后变为0,进位1。
对于最后一位1前面的每一位,需要加上-1的补码提供的1,以及后面进位过来1。该位加2后的值不变,进位1。
包括最高位符号位,也加上了-1的补码提供的1,以及后面进位过来1,该位加2后值不变,进位1,但进位溢出了,超出了机器数的范围,不起作用。
因此x-1的补码为:
PD...(x位)...D01...(y位)...1
其实,和x的补码在二进制下直接减1得到的值是一样的。

而后进行x & x-1位运算。
已知1&1=1, 0&0=1, 0&1=0, 1&0 = 0,那么有D&D = D。
列竖式:

   PD...(x位)...D10...(y位)...0
&  PD...(x位)...D01...(y位)...1
------------------------------------------PD...(x位)...D0...(y+1位)..0

x & x-1的结果相比于x,最低位的1变成了0。

每进行一次循环,x都变为x & x-1,x的补码最低位的1变为了0,或者说减少了一位1。每次循环ret增加1。当x每一位都是0时,x的值为0,跳出循环。因此ret的值为x的补码中1的位数。
当f函数传入的x值为0时,其补码为00…0,不会进入循环,ret为0,仍然满足:ret表示x中1的位数。
因此f(x)的意义是求x的补码中1的位数(x的补码中有多少位1)。

14 int g(int x)
15 {
16     return x & -x;
17 }

设x为正数:
记x的补码为:0D...(x位)...D10...(y位)...0
最高位为0,而后有x位,每位的值可能不同,最后一位1后面有y位0,y可能为0。
-x的原码为:1D...(x位)...D10...(y位)...0
-x的反码为:1R...(x位)...R01...(y位)...1 (R表示D取反后的值。如果D为0,则R为1。如果D为1,则R为0)
-x的补码为:1R...(x位)...R10...(y位)...0
求 x & -x

    0D...(x位)...D10...(y位)...0 &  1R...(x位)...R10...(y位)...0----------------------------------- 0...(x+1位)..010...(y位)...0

得到的结果是x最低位1与后面的y位0合在一起构成的数值,也就是最低位1的位权。

设x为负数:
x的补码为:1D...(x位)...D10...(y位)...0
最高位为1,而后有x位,每位的值可能不同,最后一位1后面有y位0,y可能为0。
x的反码为:1D...(x位)...D01...(y位)...1
x的原码为:1R...(x位)...R10...(y位)...0 (R为D取反后的值)
-x的原码为:0R...(x位)...R10...(y位)...0
-x的补码为:0R...(x位)...R10...(y位)...0
x & -x

  1D...(x位)...D10...(y位)...0
& 0R...(x位)...R10...(y位)...0
---------------------------------- 0...(x+1位)..010...(y位)...0

得到的结果是x最低位1与后面的y位0合在一起构成的数值,也就是最低位1的位权。
当x为0时,x & -x为0,返回0,依然符合“最低位1的位权”的概念。
因此g(x)求的是x的补码中最低位1和后面的0组成的数值,也就是最低位1的位权。

19 int main()
20 {
21     cin >> n;
22     for (int i = 0; i < n; i++) cin >> a[i];
23     for (int i = 0; i < n; i++)
24         cout << f(a[i]) + g(a[i]) << ' ';
25     cout << endl;
26     return 0;

输入由n个整数构成的a数组,遍历a数组,对a中每个元素求其补码的1的位数与最低位1与后面的0构成的数值的加和,并输出。

  • 判断题:

1. 输入的n等于1001时,程序不会发生下标越界。( )
答:F

05 int a[1000];

a数组长为1000,可以使用的下标范围为0到999。
当n为1001时,第22行以及第23行for循环最后一次进入循环体时,i的值为n-1,即1000。1000并不是a数组可以使用的下标。
如果运行该程序,会执行cin >> a[i],i为1000,会发生数组越界,产生运行时错误。
因此该题叙述错误。
2. 输入的a[i]必须全为正整数,否则程序将陷入死循环。( )
答:F
根据上述分析,当a[i]为负数或0时,f(a[i])可以求出a[i]的补码中1的位数,g(a[i])可以求出a[i]最低位1的位权。并不会陷入死循环。
最简单的例子就是0,当a[i]为0时,f(a[i])第10行不会进入循环,会之间返回0。g(a[i])会返回0,不会陷入死循环。
因此该题叙述错误。
3. 当输入为 5 2 11 9 16 10 时,输出为 3 4 3 17 5。( )
答:F
首先输入的5是数的数量n,表示有5个数。而后向数组a输入5个元素2 11 9 16 10。

a[i]a[i]的二进制补码f(a[i])g(a[i])f(a[i])+g(a[i])
2101 ( 10 ) 2 = 2 (10)_2=2 (10)2=21+2=3
1110113 ( 1 ) 2 = 1 (1)_2=1 (1)2=13+1=4
910012 ( 1 ) 2 = 1 (1)_2=1 (1)2=12+1=3
16100001 ( 10000 ) 2 = 16 (10000)_2=16 (10000)2=161+16=17
1010102 ( 10 ) 2 = 2 (10)_2=2 (10)2=22+2=4

输出应该为3 4 3 17 4,题目叙述中输出的最后一项为5,错误。

4. 当输入为 1 511998 时,输出为 18。( )
答:T
1个数,为511998。
将511998进行短除法(除基取余),将其转为二进制数,虽然有些繁琐,但也是可行的。
下面介绍相对简单的做法:观察可知511998=512000-2
512是2的幂,可以分别将512000与2转为二进制数,而后在二进制下相减,得到511998的二进制形式。
512000 = 512 ∗ 1000 = 2 9 ∗ ( 8 ∗ 125 ) = 2 9 ∗ 2 3 ∗ 125 = 2 12 ∗ 125 512000=512*1000=2^9*(8*125)=2^9*2^3*125=2^{12}*125 512000=5121000=29(8125)=2923125=212125
125 = 128 − 3 = 2 7 − 3 = ( 10000000 ) 2 − ( 11 ) 2 = ( 1111101 ) 2 125=128-3=2^7-3=(10000000)_2-(11)_2=(1111101)_2 125=1283=273=(10000000)2(11)2=(1111101)2
该数再乘以 2 12 2^{12} 212,就是将该数左移12位,得到 11111010... ( 12 位 ) . . . 0 11111010...(12位)...0 11111010...(12)...0
再减去2, 2 = ( 10 ) 2 2=(10)_2 2=(10)2,得到: 11111010... ( 12 位 ) . . . 0 − 10 = 11111001... ( 11 位 ) . . . 10 11111010...(12位)...0-10=11111001...(11位)...10 11111010...(12)...010=11111001...(11)...10
n = 511998 n = 511998 n=511998,那么 f [ n ] f[n] f[n]为n在二进制下1的位数,有16个1,所以 f [ n ] = 16 f[n]=16 f[n]=16
g [ n ] g[n] g[n]为最低位1与后面的0构成的数值,为 g [ n ] = ( 10 ) 2 = 2 g[n]=(10)_2=2 g[n]=(10)2=2
f [ n ] + g [ n ] = 16 + 2 = 18 f[n]+g[n]=16+2=18 f[n]+g[n]=16+2=18,本题正确。

5. 将源代码中 g 函数的定义(14∼17 行)移到 main 函数的后面,程序可以正常编译运行。( )
答:F
函数必须先声明或定义后,才可以运行。如果将g函数移到主函数后,主函数内调用g函数时,g函数还没有声明或定义,因此会报错。
如果想将g函数移到主函数后,需要在主函数前加一句函数声明,才可以正常运行。
函数声明如下:

int g(int);
  • 单选题:

6. 当输入为 2 -65536 2147483647 时,输出为( )。
A. 65532 33
B. 65552 32
C. 65535 34
D. 65554 33

答:B
两个数,第一个数为-65536。
根据常识应该知道, − 65536 = − 256 ∗ 256 = − 2 8 ∗ 2 8 = − 2 16 -65536=-256*256=-2^8*2^8=-2^{16} 65536=256256=2828=216
a数组为int类型,是32位有符号整型。
− 65536 -65536 65536的原码为: 10... ( 14 位 ) . . . 010... ( 16 位 ) . . . 0 10...(14位)...010...(16位)...0 10...(14)...010...(16)...0
反码为: 1... ( 15 位 ) . . . 101... ( 16 位 ) . . . 1 1...(15位)...101...(16位)...1 1...(15)...101...(16)...1
补码为: 1... ( 16 位 ) . . . 10... ( 16 位 ) . . . 0 1...(16位)...10...(16位)...0 1...(16)...10...(16)...0
该数中1的位数有16个,最低位1与后面0构成的数值为 ( 10... ( 16 位 ) . . . 0 ) 2 = 2 16 = 65536 (10...(16位)...0)_2=2^{16}=65536 (10...(16)...0)2=216=65536
输出结果为 16 + 65536 = 65552 16+65536=65552 16+65536=65552

第二个数为 2147483647 2147483647 2147483647,该数为int类型可以达到的最大值,其补码为 01... ( 31 位 ) . . . 1 01...(31位)...1 01...(31)...1 ,该数中1的位数有31个,最低位1与后面0构成的数值为1,输出结果为 31 + 1 = 32 31+1=32 31+1=32
因此正确输出为 65552 32 65552\ 32 65552 32,选B

【答案】

  1. F
  2. F
  3. F
  4. T
  5. F
  6. B
http://www.lryc.cn/news/575686.html

相关文章:

  • CSMA/CD相关习题---谢希仁课后题
  • 数据分享:医学数据集-糖尿病数据集
  • Git 使用规范与命令使用场景详解
  • 与 AI 聊天更顺畅:cat_code.py
  • MIT 6.824学习心得(1) 浅谈分布式系统概论与MapReduce
  • 【全志V821_FoxPi】3-2 Linux 5.4 SPI + XPT2046触摸(ADS7846) + tslib
  • 基于SpringBoot和Leaflet的区域冲突可视化-以伊以冲突为例
  • 重定向攻击与防御
  • 构建可无限扩展的系统:基于 FreeMarker + 存储过程 + Spring Boot 的元数据驱动架构设计
  • aws(学习笔记第四十七课) codepipeline-docker-build
  • [3D-portfolio] 版块包装高阶组件(封装到HOC) | Email表单逻辑 | 链式调用
  • 微服务分布式事务解决方案
  • 数据结构进阶 第七章 图(Graph)
  • 当ERP不再“一刀切“:ERP定制开发如何重塑企业数字神经
  • Charles抓包工具深度解析:从原理到实践的网络数据透视艺术
  • 利用云效实现自动化部署gitee仓库中的项目
  • Tailwind CSS 重用样式
  • 如果你在为理解RDA、PCA 和 PCoA而烦恼,不妨来看看丨TomatoSCI分析日记
  • 临床试验项目管理:高效推进新疗法上市
  • EXILIUM×亚矩云手机:重构Web3虚拟生存法则,开启多端跨链元宇宙自由征途
  • 用 Spark 优化亿级用户画像计算:Delta Lake 增量更新策略详解
  • Mac电脑如何搭建基于java后端的开发的各种工具服务
  • Ubuntu 下降 Linux Kernel 的版本备忘
  • 使用CSS泄露标签属性值 url路径遍历攻击 -- GPN CTF 2025 PAINting Dice
  • 【STL】深入理解 vector 的底层实现思想和使用
  • 东芝e-STUDIO 2323AMW双面复印报计数器溢出故障
  • 【CMake基础入门教程】第七课:查找并使用第三方库(以 find_package() 为核心)
  • [论文阅读] 人工智能+ | 用大语言模型给建筑合规检查“开挂“:BIM领域的自动化革命
  • python的银行柜台管理系统
  • Python 常用正则表达式大全