32 位 int 类型的存储范围是( )?
🧭 补码表示
32 位有符号整数用补码表示,范围是 −231 ~ 231−1 = −2147483648 ~ +2147483647。负数比正数多一个(0 占了正半区一个位置)。
试卷:CSP 2024 入门级(Junior)第一轮 · 单项选择 + 阅读程序 + 完善程序
用法:先独立作答,再点「显示解析」核对分步推理;右上角可切换 浅色/深色 配色。
CSP-J 入门级第一轮,覆盖基础常识与基本算法。本卷可归为 8 大板块。
Q1 int 范围、Q6 基本数据类型、Q8 字符与 ASCII。
Q2 多进制混合运算、Q4 格雷码、Q5 字节/位换算。
Q3 分部门选人、Q14 捆绑法排列。
Q11 图的度、Q12 二叉树遍历、Q13 栈的合法出栈序列。
Q9 二分查找次数、Q15 编译器作用、Q7 循环语句、Q10 操作系统。
素数判断与统计、最小代价爬楼梯 DP、递归求值。
判断完全平方数、汉诺塔递归。
循环边界 i*i<=n、递归闭式 a×(b+1)、出栈合法性、捆绑/插空。
32 位 int 类型的存储范围是( )?
32 位有符号整数用补码表示,范围是 −231 ~ 231−1 = −2147483648 ~ +2147483647。负数比正数多一个(0 占了正半区一个位置)。
计算 (148 − 10102) × D16 − 11012 的十进制结果是( )?
| 原数 | 十进制 |
|---|---|
| 148 | 1×8+4 = 12 |
| 10102 | 8+2 = 10 |
| D16 | 13 |
| 11012 | 8+4+1 = 13 |
(12 − 10) × 13 − 13 = 2×13 − 13 = 26 − 13 = 13。
10 名员工分为 A(4)、B(3)、C(3) 三个部门,选 4 人且每部门至少 1 人,有多少种选法?
4 人分到 3 个部门、每部门 ≥1,名额只能是 (2,1,1) 的某个排列:
| 分配 (A,B,C) | 算式 | 种数 |
|---|---|---|
| (2,1,1) | C(4,2)·C(3,1)·C(3,1)=6·3·3 | 54 |
| (1,2,1) | C(4,1)·C(3,2)·C(3,1)=4·3·3 | 36 |
| (1,1,2) | C(4,1)·C(3,1)·C(3,2)=4·3·3 | 36 |
合计 54+36+36 = 126。
以下哪个序列是数字 0~7 的 4 位二进制格雷码?
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 格雷码 | 0000 | 0001 | 0011 | 0010 | 0110 | 0111 | 0101 | 0100 |
序列与 D 完全一致。格雷码的特征:相邻两数只差 1 个二进制位(0101→0100 只差末位),A、B、C 都在某处跳变了 ≥2 位。
i ^ (i>>1) 生成。检验选项时看每对相邻是否只差一位。1KB=1024 字节,1MB=1024KB,那么 1MB 是多少二进制位(bit)?
1MB = 1024×1024 字节 = 1048576 字节。1 字节 = 8 位,所以 ×8 = 8388608 位。
即 220 × 23 = 223 = 8388608。
以下哪个不是 C++ 中的基本数据类型?
int、float、char 都是 C++ 内置(基本)数据类型。struct 是用来构造自定义复合类型的关键字,本身不是基本数据类型。
以下哪个不是 C++ 中的循环语句?
C++ 的循环只有 for、while、do-while。repeat-until 是 Pascal 语言的循环,C++ 中没有。
do-while 是「先执行后判断」(至少执行一次),while 是「先判断后执行」。在 C/C++ 中,(char)('a' + 13) 与下面哪个值相等?
'a' 之后第 13 个字母:a(0) b c d e f g h i j k l m(12) n(13)。所以 'a'+13 对应 'n'。
也可记 'a'=97,97+13=110,ASCII 110 = 'n'。
有序表有 1000 个元素,用二分法查找 X 最多需要比较多少次?
29=512 < 1000 ≤ 1024 = 210。所以最多 ⌈log₂1000⌉ = 10 次。
直观:1000 → 500 → 250 → 125 → 63 → 32 → 16 → 8 → 4 → 2 → 1,约 10 步缩到 1。
下面哪一个不是操作系统的名字?
Linux、Windows、macOS 都是操作系统;Notepad(记事本)是 Windows 上的一个文本编辑应用程序,不是操作系统。
在无向图中,所有顶点的度数之和等于( )。
每条无向边连接两个顶点,对这两个顶点各贡献 1 度,所以一条边贡献 2 度。度数总和 = 2 × 边数。
前序 [A,B,D,E,C,F,G]、中序 [D,B,E,A,F,C,G],则后序遍历是?
前序首位 A = 根。中序中 A 把序列分成 左[D,B,E]、右[F,C,G]。
左子树:前序[B,D,E]→根 B,中序[D,B,E]→左 D、右 E。
右子树:前序[C,F,G]→根 C,中序[F,C,G]→左 F、右 G。
后序 = 左→右→根,递归得:(D,E,B)(F,G,C)A = D,E,B,F,G,C,A。
依次入栈 1 2 3 4 5 6(1 先 6 后),下面哪种出栈顺序不可能?
| 要输出 | 操作 | 栈(顶在右) |
|---|---|---|
| 1 | 压1,弹1 | 空 |
| 3 | 压2,压3,弹3 | 2 |
| 5 | 压4,压5,弹5 | 2 4 |
| 2 | 栈顶是 4,却要弹 2 ✘ | 2 4 |
要输出 2 时,2 被压在 4 下面,栈顶是 4,无法先弹 2 → 不可能。
5 个男生、3 个女生站一排,3 个女生必须相邻,有多少种排法?
把 3 个女生看成 1 个「大块」,与 5 个男生共 6 个单位排列:6! = 720 种。
大块内部 3 个女生再排列:3! = 6 种。
相乘:720 × 6 = 4320。
编译器的主要作用是什么?
编译器把高级语言源代码翻译成机器代码(目标代码)。A 是解释器/直接执行的描述;C 是调试器(debugger);D 是操作系统/运行时的工作。
📄 阅读程序(一)· 素数判断与统计
判断题正确填「正确」、错误填「错误」。统计/求和 2~n 之间的素数。
bool isPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) // 只需试到 √n
if (n % i == 0) return false;
return true;
}
int countPrimes(int n){ int c=0; for(int i=2;i<=n;i++) if(isPrime(i)) c++; return c; }
int sumPrimes(int n){ int s=0; for(int i=2;i<=n;i++) if(isPrime(i)) s+=i; return s; }
int main(){ int x; cin>>x; cout<<countPrimes(x)<<" "<<sumPrimes(x)<<endl; }
当输入为 10 时,第一个输出为 4,第二个输出为 17。
2, 3, 5, 7 共 4 个 → countPrimes(10)=4;它们的和 2+3+5+7 = 17 → sumPrimes(10)=17。输出「4 17」。
若将 isPrime 中条件 i*i<=n 改为 i<=n/2,输入 20 时 countPrimes(20) 变为 6。
合数 n 的最小因子一定 ≤ √n ≤ n/2,所以试到 n/2 依然能找出所有合数、判素数结果不变。countPrimes(20) 仍统计 2,3,5,7,11,13,17,19 共 8 个,并非 6。
区别只是 i≤n/2 比 i*i≤n 慢(试的次数更多),结果一样。
sumPrimes 函数计算的是从 2 到 n 之间所有素数之和。
for(i=2;i<=n;i++) if(isPrime(i)) s+=i; 正是把 2~n 中每个素数累加,功能描述正确。
当输入为 50 时,sumPrimes(50) 的输出为?
2+3+5+7+11+13+17+19+23+29+31+37+41+43+47。
分组求和:(2+3+5+7+11+13+17+19)=77;(23+29+31+37)=120;(41+43+47)=131。合计 77+120+131 = 328。
若将 for(int i=2;i*i<=n;i++) 改为 for(int i=2;i<=n;i++),输入 10 时程序的输出?
循环跑到 i=n 时,n % n == 0 成立 → 直接 return false。于是任何 n≥2 都被判为非素数!
结果 countPrimes(10)=0、sumPrimes(10)=0,输出「0 0」,完全错误 → 选 A。
i*i<=n 放宽到 i<=n 不是「变慢但正确」,而是直接算错。📄 阅读程序(二)· 最小代价爬楼梯(DP)
dp[i] = 到达第 i 阶的最小花费,可从 i−1 或 i−2 上来;最终可停在第 n 或 n−1 阶。
int compute(vector<int>& cost) {
int n = cost.size();
vector<int> dp(n+1, 0);
dp[1] = cost[0];
for (int i = 2; i <= n; i++)
dp[i] = min(dp[i-1], dp[i-2]) + cost[i-1]; // 从前一阶或前两阶上来
return min(dp[n], dp[n-1]); // 终点可停 n 或 n-1
}
当 cost = {10, 15, 20} 时,程序输出 15。
| i | dp[i] = min(dp[i−1],dp[i−2]) + cost[i−1] |
|---|---|
| 1 | dp[1]=cost[0]=10 |
| 2 | min(10,0)+15 = 15 |
| 3 | min(15,10)+20 = 30 |
return min(dp[3], dp[2]) = min(30, 15) = 15。
如果将 dp[i-1] 改为 dp[i-3],程序可能会产生编译错误。
dp[i-3] 语法完全合法,能正常通过编译。只是当 i=2 时下标变成 dp[−1],属于运行期越界访问(未定义行为),并不会报「编译错误」。
所以「可能产生编译错误」的说法错误。
[] 不做边界检查。程序总是输出 cost 数组中最小的元素。
反例 cost={10,15,20}:最小元素是 10,但程序输出 15。它累加的是一条「跳跃路径」上的花费之和,并非单个最小值。
当 cost = {1,100,1,1,1,100,1,1,100,1} 时,程序输出?
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| cost | 1 | 100 | 1 | 1 | 1 | 100 | 1 | 1 | 100 | 1 |
| dp | 1 | 100 | 2 | 3 | 3 | 103 | 4 | 5 | 104 | 6 |
return min(dp[10], dp[9]) = min(6, 104) = 6。(每遇到 100 就靠「跳两阶」绕开。)
当 cost = {10,15,30,5,5,10,20} 时,程序输出?
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| cost | 10 | 15 | 30 | 5 | 5 | 10 | 20 |
| dp | 10 | 15 | 40 | 20 | 25 | 30 | 45 |
逐项:dp3=min(15,10)+30=40;dp4=min(40,15)+5=20;dp5=min(20,40)+5=25;dp6=min(25,20)+10=30;dp7=min(30,25)+20=45。
return min(dp[7], dp[6]) = min(45, 30) = 30。
若将 min(dp[i-1],dp[i-2])+cost[i-1] 改为 dp[i-1]+cost[i-2],cost = {5,10,15} 时输出?
dp[1]=cost[0]=5。dp[i]=dp[i−1]+cost[i−2]:
| i | dp[i]=dp[i−1]+cost[i−2] |
|---|---|
| 2 | dp[1]+cost[0] = 5+5 = 10 |
| 3 | dp[2]+cost[1] = 10+10 = 20 |
return min(dp[3], dp[2]) = min(20, 10) = 10。
📄 阅读程序(三)· 递归求值
customFunction(a,b) 递归累加;main 输出 pow(result, 2)。关键闭式:customFunction(a,b) = a×(b+1)。
int customFunction(int a, int b) {
if (b == 0) return a;
return a + customFunction(a, b - 1); // 累加 (b+1) 个 a
}
int main(){ int x,y; cin>>x>>y; cout<<pow(customFunction(x,y), 2)<<endl; }
当输入为 2 3 时,customFunction(2, 3) 的返回值为 64。
customFunction(2,3) = 2+2+2+2 = 8(= 2×(3+1))。它的返回值是 8。
64 是 main 里 pow(8,2) 的最终输出,不是 customFunction 的返回值。题目问的是返回值,故说「返回值为 64」错误。
当 b 为负数时,customFunction(a,b) 会陷入无限递归。(错题:同时选「正确」和「错误」均得分)
从逻辑看,b 为负时 b==0 永不成立,b 不断减小,确实会「无限递归」直到栈溢出崩溃——所以说「正确」有道理。
但严格说,递归并非真正「无限」,而是因栈溢出而终止(异常退出),措辞有歧义。官方因此判为错题,两个选项都给分。
当 b 的值越大,程序的运行时间越长。(错题:同时选「正确」和「错误」均得分)
b 越大,递归层数越多,运行时间一般越长——说「正确」合理。但增量极小、且未限定 b 非负等边界条件,命题不够严谨,官方判为错题,两项都给分。
当输入为 5 4 时,customFunction(5, 4) 的返回值为?
customFunction(5,4) = 5×(4+1) = 5×5 = 25。(即 5+5+5+5+5。)
注意题目问的是返回值 25,不是程序输出 pow(25,2)=625。
如果输入 x=3、y=3,则程序的最终输出为?
customFunction(3,3) = 3×(3+1) = 12。程序输出 pow(12,2) = 12² = 144。
若将递归改为 return a + customFunction(a-1, b-1);,输入 3 3,最终输出为?
| 调用 | 展开 |
|---|---|
| f(3,3) | 3 + f(2,2) |
| f(2,2) | 2 + f(1,1) |
| f(1,1) | 1 + f(0,0) |
| f(0,0) | b==0 → 返回 0 |
返回值 = 3+2+1+0 = 6。最终输出 pow(6,2) = 6² = 36。
a-1 后每层的被加数依次递减:3+2+1+0,相当于 0~a 求和。再平方得 36。🧩 完善程序(一)· 判断完全平方数
给定正整数 num,判断是否存在正整数 x 使 x²=num。在 1~⌊√num⌋ 范围内枚举 i,看 i²是否等于 num。补全 ①~⑤。
bool isSquare(int num) {
int i = __①__;
int bound = __②__;
for (; i <= bound; ++i) {
if (__③__) return __④__;
}
return __⑤__;
}
① 处应填?
要找 x 使 x²=num,x 从 1 起试(1²=1 也要能判,如 num=1)。所以 i 初值 = 1。
② 处(枚举上界 bound)应填?
若 num 是完全平方数,则 x=√num 恰是整数,必须让 i 能取到它。所以 bound = (int)floor(sqrt(num))。
选 A(−1)会漏掉 x=√num 本身(如 num=16,bound 变 3,取不到 4);C、D 用 num/2 范围错误。
i<=bound,确保 x=√num 被检查到。③ 处(判定条件)应填?
用相等比较 ==(不是赋值 =),且是 i*i(平方)而非 2*i。故填 num == i * i。
A、C 是赋值(语法上会把值写进 num),B 是 2*i(判断的是 2 倍不是平方),都不对。
= 是赋值、== 是判等;完全平方数判定核心是 i*i == num。④ 处(找到时返回什么)应填?(找到平方根时)
能进入 if (num == i*i) 这一支,说明 num 确实是完全平方数,应 return true。
(官方注「有多个可能选项」:因为此刻 num==i*i 本身也为真,但给出的选项里只有 C 是恒真值,故选 C。)
bool 返回值:找到目标返回 true,循环结束都没找到返回 false。⑤ 处(循环结束后返回)应填?
循环跑遍 1~⌊√num⌋ 都没有 i 满足 i²=num,说明 num 不是完全平方数,返回 false。
🧩 完善程序(二)· 汉诺塔
dfs(i, src, tmp, tgt) 表示把 i 个盘从 src 经 tmp 移到 tgt。补全 ①~⑤。
void move(char src, char tgt){ cout<<"从柱子"<<src<<"挪到柱子"<<tgt<<endl; }
void dfs(int i, char src, char tmp, char tgt) {
if (i == __①__) { move(__②__); return; } // 只剩 1 个盘
dfs(i - 1, __③__); // 先把上面 i-1 个挪到 tmp
move(src, tgt); // 最大盘 src → tgt
dfs(__⑤__, __④__); // 再把 i-1 个从 tmp 挪到 tgt
}
int main(){ int n; cin>>n; dfs(n, 'A', 'B', 'C'); }
① 处(递归边界)应填?
当 i==1 时只有一个盘,直接从 src 移到 tgt 即可,是递归出口。
② 处(边界时的移动)应填?
目标是把盘移到 tgt,所以 move(src, tgt)。
③ 处(第一次递归的参数)应填?
这一步要把 i−1 个盘从 src 移到 tmp,借助 tgt 作临时。按 dfs(i−1, 源, 临时, 目标) 的参数顺序填:源=src、临时=tgt、目标=tmp → src, tgt, tmp。
④ 处(第二次递归的柱子参数)应填?
第三步要把 i−1 个盘从 tmp 移到 tgt,借助 src 作临时。按 dfs(i−1, 源, 临时, 目标):源=tmp、临时=src、目标=tgt → tmp, src, tgt。
⑤ 处(第二次递归的盘数)应填?
移完最大盘后,剩下要搬的还是上面那 i−1 个盘,所以盘数填 i - 1。
整体完整代码对 n=3 输出 7 步(A→C, A→B, C→B, A→C, B→A, B→C, A→C),正是标准汉诺塔解。