一个 32 位无符号整数可以表示的最大值,最接近下列哪个选项?
🧭 估算步骤
32 位无符号最大值 = 232 − 1 = 4 294 967 295 ≈ 4.29×109。
速算:210=1024≈103,所以 232 = 22×(210)3 ≈ 4×109。最接近 A。
int 最大约 231−1 ≈ 2.1×109。试卷:CSP 2025 入门级(Junior)第一轮 · 单项选择 + 阅读程序 + 完善程序
用法:每题先独立作答,再点「显示解析」核对分步推理;右上角可切换 浅色/深色 配色。
CSP-J 初赛覆盖面广。本卷可归为 8 大板块,复习时按板块查漏补缺。
Q1 无符号范围 232、Q13 十进制/八进制/十六进制互转。记牢 210≈103。
Q2 x&(x-1) 清除最低位的 1。
Q3 递归求值、Q8 斐波那契取模的周期性。
Q4 哈夫曼树、Q5 图入度/出度、Q14 完全二叉树、Q15 栈与队列。
Q6 组合数+补集、Q11 网格路径 = 组合数。
Q7 布尔表达式等价、Q9 string、Q10 引用 vs 值传递。
Q12 冒泡交换次数 = 逆序对数。
欧几里得+两两互质、双指针贪心分组、LCS 动态规划、行程长度编码、摩尔投票。
一个 32 位无符号整数可以表示的最大值,最接近下列哪个选项?
32 位无符号最大值 = 232 − 1 = 4 294 967 295 ≈ 4.29×109。
速算:210=1024≈103,所以 232 = 22×(210)3 ≈ 4×109。最接近 A。
int 最大约 231−1 ≈ 2.1×109。执行 int x = 255; cout << (x & (x - 1)); 后输出是?
255 = 1111 1111,254 = 1111 1110。
按位与:1111 1111 & 1111 1110 = 1111 1110 = 254。
x & (x-1) 会清除 x 最低位的那个 1。反复执行可统计二进制中 1 的个数(Brian Kernighan 算法)。& 误当成「减 1」或直接得 0。calc(5) 的返回值是多少?
int calc(int n) {
if (n <= 1) return 1;
if (n % 2 == 0) return calc(n / 2) + 1;
else return calc(n - 1) + calc(n - 2);
}
| n | 规则 | calc(n) |
|---|---|---|
| 0,1 | n≤1 | 1 |
| 2 | 偶:calc(1)+1 | 2 |
| 3 | 奇:calc(2)+calc(1)=2+1 | 3 |
| 4 | 偶:calc(2)+1=2+1 | 3 |
| 5 | 奇:calc(4)+calc(3)=3+3 | 6 |
calc(0) 也命中 n≤1 返回 1。用 5 个权值 10, 12, 15, 20, 25 构造哈夫曼树,带权路径长度 WPL 是多少?
| 当前集合 | 取两最小合并 | 新节点 |
|---|---|---|
| 10 12 15 20 25 | 10+12 | 22 |
| 15 20 22 25 | 15+20 | 35 |
| 22 25 35 | 22+25 | 47 |
| 35 47 | 35+47 | 82 |
WPL = 所有「内部节点权值」之和 = 22+35+47+82 = 186。
有向图中,所有顶点入度之和等于所有顶点出度之和,这个总和等于?
每条有向边 u→v 恰好给 u 贡献 1 个出度、给 v 贡献 1 个入度。所以入度总和 = 出度总和 = 边数。
从 5 名男生、4 名女生中选 4 人,要求男女都有,有多少种选法?
9 人选 4:C(9,4) = 126。减去「全男」C(5,4)=5 与「全女」C(4,4)=1。
126 − 5 − 1 = 120。
设 a,b,c 为布尔变量,(a && b) || (!c && a) 与下列哪个表达式 不始终相等?
提取公因子 a:原式 = a && (b || !c),记为 E。逐项比对:
| 选项 | 化简 | = E ? |
|---|---|---|
| A | 本身就是 a&&(b||!c) | ✔ |
| B | a&&(a||!c)&&(b||!c)=a&&(b||!c) | ✔ |
| C | a&&(!b||c),括号与 (b||!c) 不同 | ✘ 不等 |
| D | (a&&b)||(a&&!c)=a&&(b||!c) | ✔ |
反例验证 C:取 b=0,c=1 → E 内 (b||!c)=0,而 C 内 (!b||c)=1,结果不同。
a&&(x||y)=(a&&x)||(a&&y)、吸收律 a&&(a||x)=a、德摩根 !(!a||!b)=a&&b。拿不准时直接列真值表(8 行)。f[0]=1, f[1]=1,n≥2 时 f[n]=(f[n−1]+f[n−2]) % 7。则 f[2025] = ?
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| f | 1 | 1 | 2 | 3 | 5 | 1 | 6 | 0 | 6 | 6 | 5 | 4 | 2 | 6 | 1 | 0 | 1 | 1 |
f[16]=1, f[17]=1 与开头相同 → 周期为 16。
2025 mod 16 = 9(16×126=2016,2025−2016=9)→ f[2025]=f[9]=6。
下列关于 C++ string 类的说法,正确的是?
| 选项 | 判断 |
|---|---|
| A | 错:string 可变长,能 +=、append、resize |
| B | 对:s + 'c' 合法,char 会被接到末尾 |
| C | 错:length() 与 size() 完全等价 |
| D | 错:内部即便有 '\0' 也不计入 length() |
char[](靠 '\0' 结尾)不同。调用 solve 后,x 和 y 的值分别是?
void solve(int &a, int b) { // a 是引用, b 是副本
a = a + b;
b = a - b;
a = a - b;
}
int main() {
int x = 5, y = 10;
solve(x, y);
}
| 语句 | a (= x) | b (副本) | y |
|---|---|---|---|
| 进入时 | 5 | 10 | 10 |
| a=a+b | 15 | 10 | 10 |
| b=a−b | 15 | 5 | 10 |
| a=a−b | 10 | 5 | 10 |
函数修改 a 会改到 x(引用),但 b 是 y 的副本,y 始终是 10。结果 x=10, y=10。
int &a 是引用(改实参),int b 是值传递(只改副本)。经典三步交换只有在两个参数都按引用时才成立。机器人从 (1,1) 出发,每次只能向右或向下走一格,到达 (4,5) 有多少种路径?
行 1→4 需向下 3 步;列 1→5 需向右 4 步。共 7 步,从中选 3 步向下:
C(7,3) = (7×6×5)/(3×2×1) = 35。
用冒泡排序对 {6, 1, 5, 2, 4} 升序排序,需进行多少次元素交换?
统计每个数后面比它小的个数(逆序对):
| 元素 | 后面比它小的 | 个数 |
|---|---|---|
| 6 | 1,5,2,4 | 4 |
| 1 | — | 0 |
| 5 | 2,4 | 2 |
| 2 | — | 0 |
逆序对总数 = 4+0+2+0 = 6,即交换 6 次。
十进制 72010 与八进制 2708 的和,用十六进制表示是多少?
2708 = 2×82+7×8+0 = 128+56 = 184。
720 + 184 = 904。
904 ÷ 16 = 56 余 8;56 ÷ 16 = 3 余 8;3 ÷ 16 = 0 余 3 → 自下而上读 38816。
校验:3×256 + 8×16 + 8 = 768+128+8 = 904 ✔。
一棵包含 1000 个结点的完全二叉树,叶子结点的数量是多少?
完全二叉树按 1..n 编号,结点 i 的孩子是 2i、2i+1。结点 i 是叶子 ⟺ 2i > n。
n=1000:有孩子的结点是 i ≤ 500(因 2×500=1000≤1000),共 500 个内部结点;其余 1000−500 = 500 个为叶子。
空栈 S、空队列 P。按序处理 A: 7,5,8,3,1,4,2。奇数压入 S;偶数且 S 非空则弹出栈顶加入 P 末尾;偶数且 S 空则不操作。最终 P 是什么?
| 读入 | 奇/偶 | 操作 | 栈 S | 队列 P |
|---|---|---|---|---|
| 7 | 奇 | 压栈 | 7 | — |
| 5 | 奇 | 压栈 | 7 5 | — |
| 8 | 偶 | 弹栈顶 5 | 7 | 5 |
| 3 | 奇 | 压栈 | 7 3 | 5 |
| 1 | 奇 | 压栈 | 7 3 1 | 5 |
| 4 | 偶 | 弹栈顶 1 | 7 3 | 5 1 |
| 2 | 偶 | 弹栈顶 3 | 7 | 5 1 3 |
处理完毕,P = 5,1,3(栈剩 7 未弹出)。
📄 阅读程序(一)· 统计两两互质的三元组个数
说明:判断题正确填 A、错误填 B。下面三段共享这份程序。
inline int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b); // 欧几里得(辗转相除)
}
int main() {
int n; scanf("%d", &n);
int ans = 0;
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
for (int k = j + 1; k <= n; ++k)
if (gcd(i,j)==1 && gcd(j,k)==1 && gcd(i,k)==1)
++ans; // 三个数两两互质才计数
printf("%d\n", ans);
}
当输入为 2 时,程序并不会执行那条 if 判断语句。
n=2 时:i=1, j=2,k 的范围是 j+1=3 到 n=2,空区间,最内层循环一次都不执行,if 自然不会执行。
将 if 中的 && gcd(i,k)==1 删去,不会影响程序运行结果。
反例 i=2, j=3, k=4:gcd(2,3)=1、gcd(3,4)=1,但 gcd(2,4)=2≠1。删去第三个条件后 (2,3,4) 会被错误计入,结果偏大。
所以这个条件不可省,删去会改变结果 → 题述「不会影响」错误。
当输入 n ≥ 3 时,程序总是输出一个正整数。(原卷标注为错题,请选 B 得分)
本题在原卷中被认定为有争议/错题,官方规定选 B 即得分。(事实上 n≥3 时如 (1,2,3) 必两两互质,ans≥1,故命题描述并不构成「错误」,属出题瑕疵。)
将递归 gcd(b, a % b) 改成 gcd(a, a % b) 后,程序可能出现的问题是?
新调用 gcd(a, a%b) 中第一个参数永远是 a 不变,第二个参数 a%(上一个) 严格递减,最终变 0 返回 a。所以 gcd(x,y) 恒返回 x。
于是判断 gcd(i,j)==1 变成 i==1,gcd(j,k)==1 变成 j==1(而 j≥2 不可能)。条件永不成立 → ans 恒为 0,比原答案小。
第二个参数严格递减到 0,不会死循环;数值很小,不会溢出。故选 B。
当输入为 8 时,输出为?
共 C(8,3)=56 个三元组,逐一筛掉含公因子的。直接编程/枚举得 25 个。
速记技巧:偶数 {2,4,6,8} 中任意两个不互质,所以一个三元组里最多含 1 个偶数;据此分类计数也能得到 25。
调用 gcd(36, 42)(原正确版本)返回?
gcd(36,42) → gcd(42,36) → gcd(36,42%36=6) → gcd(6,36%6=0) → 返回 6。
验证:36=6×6,42=6×7,最大公约数 6。(B 是最小公倍数 252。)
📄 阅读程序(二)· 排序去重 + 双指针贪心分组
排序去重后,求把这些数分成若干组、每组「最大−最小 ≤ k」所需的最少组数。
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
std::sort(a + 1, a + n + 1); // 第13行
n = std::unique(a + 1, a + n + 1) - a - 1; // 第14行:去重,n 变为去重后长度
for (int i = 1, j = 0; i <= n; ++i) {
for (; j < i && a[i] - a[j + 1] > k; ++j) ; // 双指针推进 j
ans[i] = ans[j] + 1; // 第18行
}
printf("%d\n", ans[n]);
当输入为 3 1 / 3 2 1(n=3, k=1, 数组 3 2 1)时,输出结果为 2。
排序去重后 a = [1,2,3],k=1。
| i | a[i] | j 推进 | ans[i]=ans[j]+1 |
|---|---|---|---|
| 1 | 1 | j=0(1−1=0>1? 否) | ans[0]+1=1 |
| 2 | 2 | j=0(2−1=1>1? 否) | ans[0]+1=1 |
| 3 | 3 | 3−1=2>1→j=1;3−2=1>1? 否 | ans[1]+1=2 |
输出 ans[3] = 2(分成 {1,2} 和 {3})。
设输入的 n 为正整数,则输出的答案一定 ≤ n 且 ≥ 1。
每个数自成一组时组数最多 = 去重后元素个数 ≤ 输入 n,故 ans ≤ n;至少有 1 组,故 ans ≥ 1。由 ans[i]=ans[j]+1 且 j<i 也可得 ans[i] ≤ i。
将第 14 行的去重 n = std::unique(...) - a - 1; 删去后,有可能出现与原本不同的输出。
数组已排好序,重复值相邻且差为 0 ≤ k,会被自然并入同一组,不会增加组数。分组数只取决于「有哪些不同的值」,与重复多少次无关。
因此保留或删除去重,输出 ans[n] 完全相同 → 题述「有可能不同」错误。
设 a 数组与 k 均为正整数,执行第 18 行 ans[i]=ans[j]+1 时,一定满足的条件不包括?
| 条件 | 是否恒成立 | 原因 |
|---|---|---|
| A:j<i | ✔ 恒成立 | 让 j=i 需 a[i]−a[i]=0>k,因 k≥1 不可能,故 j 最多到 i−1 |
| B:a[i]−a[j]>k | ✘ 不一定 | 若 j 这轮没推进(如 i=1,j=0),a[i]−a[j] 可能 ≤ k |
| C:j<n | ✔ 恒成立 | j<i≤n |
| D:a[j]<a[i] | ✔ 恒成立 | j<i 且数组严格递增;j=0 时 a[0]=0<a[i] |
所以「一定满足的条件不包括」的是 B。
a[i]−a[j+1] ≤ k(循环跳出),而非对 a[j] 的保证。分析循环不变量要紧扣「跳出条件」。当 n=100, k=2, a = {1,2,…,100} 时,输出为?
极差 ≤ k=2 意味着每组最多含 3 个连续整数(如 {1,2,3}、{4,5,6}…)。100 个数:
⌈100 / 3⌉ = 34 组(前 33 组各 3 个共 99 个,第 34 组装第 100 个)。
从递推看 ans[i]=ans[i−3]+1,得 ans[i]=⌈i/3⌉,ans[100]=34。
设 a、k 均为正整数但 a 不一定有序。若误删第 13 行 std::sort(...),程序可能出现的问题是?
数组无序后,a[i]−a[j+1] 经常 ≤ k 甚至为负,双指针 j 推进得更少 → 停在更靠前、ans 更小的位置 → ans[i]=ans[j]+1 整体偏小。
内层 for(; j<i && ... ; ++j) 有 j<i 上界且 j 只增不减,不可能死循环,故排除 C、D。
j<i 上界的循环不会失控。📄 阅读程序(三)· 最长公共子序列(LCS)
经典二维 DP:f[i][j] = a 前 i 个与 b 前 j 个的最长公共子序列长度,输出 f[n][n]。
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
f[i][j] = std::max(f[i][j], std::max(f[i-1][j], f[i][j-1])); // 第18行
if (a[i] == b[j])
f[i][j] = std::max(f[i][j], f[i-1][j-1] + 1);
}
printf("%d\n", f[n][n]);
当输入 4 / 1 2 3 4 / 1 3 2 2 时,输出为 2。
a=1,2,3,4 严格递增,所以公共子序列必须是 b 的「上升子序列」。b=1,3,2,2 的最长上升子序列是 1,3 或 1,2,长度 2。
故 f[4][4]=2。
程序运行完毕后,对所有 1 ≤ i,j ≤ n,都一定有 f[i][j] ≤ f[n][n]。
每个 f[i][j] 都从 f[i−1][j]、f[i][j−1] 取 max 转移,所以 f 关于 i、j 都单调不减。f[n][n] 是右下角,是全表最大值,故对任意 i,j≤n 有 f[i][j] ≤ f[n][n]。
将第 18 行(从上方/左方继承 max 的那行)删去,不影响程序运行结果。
第 18 行负责把上方 f[i−1][j]、左方 f[i][j−1] 的结果继承下来——这是「子序列可不连续」的关键。
删去后,f[i][j] 只在 a[i]==b[j] 时由 f[i−1][j−1]+1 得到,否则保持 0,于是计算的是最长公共子串(连续),与原 LCS 结果一般不同 → 题述「不影响」错误。
输出的答案满足的性质有?
LCS 长度 ≤ 两串较短长度 = n(A 对);长度非负 ≥ 0(B 对);若 a、b 无任何公共元素则 LCS=0,所以「不一定 ≥1」(C 对)。三者皆真 → D。
若在循环前对 a、b 都加上 std::sort(各自升序),则答案会?
两个都升序后,LCS 等于 a、b 的「多重集公共元素个数」。而原 LCS ≤ 公共元素个数(任何公共子序列只能用公共元素)。所以排序后 ≥ 原值,即变大或不变。
例:a=2,1,b=1,2。原 LCS=1;排序后都成 1,2,LCS=2,变大。
若输入 a = {1,2,…,n},b 中数字均为 1~n 的正整数,则该程序等价于?
a 是严格递增的 1,2,…,n,任何与 a 的公共子序列必须严格递增。于是 LCS(a,b) = b 的最长(严格)上升子序列长度。
🧩 完善程序(一)· 行程长度编码 RLE 解码
规则:字符连续出现 N≥2 次写成「字符+数字 N」(如 A12 = 12 个 A);只出现 1 次就写字符本身(如 B)。原串不含数字。补全 ①~⑤。
string z; cin >> z;
string s = "";
for (int i = 0; i < z.length(); ) {
char ch = z[i];
if (__①__ && isdigit(z[i + 1])) { // 后面跟着数字 → 带重复次数
i++;
int count = 0;
while (i < z.length() && isdigit(z[i])) {
count = __②__;
i++;
}
for (int j = 0; j < __③__; ++j) s += ch;
} else {
s += __④__;
__⑤__;
}
}
cout << s << endl;
① 处应填?
条件里要访问 z[i+1],必须先确保下标合法 i+1 < z.length(),否则末尾字符会越界。短路求值保证越界检查在前。
&& 短路特性把边界检查写在左边。② 处应填(逐位拼出多位数字 count)?
把字符数字串转成整数:每读一位,旧值 ×10 再加上新数字 z[i]-'0'。如 "12":count 0→1→12。
z[i]-'0';多位拼接用 count = count*10 + 数字。③ 处应填(重复输出 ch 的次数)?
编码规则里数字 N 表示该字符总共出现 N 次(不是额外次数)。本分支尚未输出过 ch,所以要输出 count 次。验证 "A12" → 输出 12 个 A ✔。
④ 处应填(单字符分支,输出当前字符)?
进入 else 说明当前字符后面没有数字,按规则它只出现 1 次,直接输出 ch(即 z[i])。
⑤ 处应填(else 分支推进下标)?
else 分支处理了 1 个字符,需把下标前进 1:i++。(注意 for 头部没有自增,必须在循环体内推进,否则死循环。)
对照 if 分支:i 已在读字符和数字时自行推进,所以 else 分支单独补一个 i++。
for(...; ...; ))时,循环体内必须自己更新下标,且每条路径都要推进。🧩 完善程序(二)· 找出一个百分百确定的精明人(摩尔投票)
精明人严格过半(k > N/2)。query(i,j) 返回 i 对 j 的判断(true=精明)。用「互相抵消」找出多数派代表。补全 ①~⑤。
int candidate = 0; int count = __①__; for (int i = 1; i < N; ++i) { if (__②__) { // 票数归零,换人 candidate = i; count = 1; } else { if (__③__) { // 与候选人「冲突」→ 抵消 __④__; } else { count++; // 一致 → 票数 +1 } } } cout << __⑤__ << endl;
① count 的初值应填?
candidate 一开始是第 0 个人,他自己代表 1 票,所以 count 初值 = 1。循环从 i=1 开始处理其余人。
② 「换候选人」的触发条件应填?
当 count 被抵消到 0,说明当前候选人已「失去支持」,就让新来的 i 当候选人、票数重置为 1。这是摩尔投票的标准换人时机。
③ 「与候选人冲突(应抵消)」的条件应填?
两个精明人会互相确认(双方都 true)。只要有任意一方说对方是糊涂(false),就至少有一个糊涂人,应当抵消(count--)。
| 组合 | query 结果 | 用「或」判定 |
|---|---|---|
| 精明 + 精明 | 都 true | 不冲突 → count++(正确,绝不误伤) |
| 精明 + 糊涂 | 精明方说 false | 冲突 → 抵消(正确) |
若用「与」(C):糊涂人碰巧回 true 时就不抵消,会把糊涂人当成同伴累加,可能选错。故必须用「或」。
④ 冲突时应执行?
冲突意味着候选人与 i 中至少一个糊涂,把这对「一同移除」,体现为候选人少一票:count--。下一轮若 count 归 0 再换人。
⑤ 最终输出应填?
由于精明人严格过半,所有抵消都不会消掉「精明 vs 精明」,最后留下的 candidate 必属多数派,即一个确定的精明人。输出 candidate。