# CSP-J 2026 第一轮试卷:参考答案与详细解析 > 根据用户提供的《CSP-J-2026-试卷与答案.pdf》整理,题号及选项对应该文件。本文对全部 43 题逐题推导,所得答案与文件卷末参考答案一致。判断题中 A 表示正确,B 表示错误。本文沿用所提供文件的标题,不另行认定其官方来源。 ## 一、答案速查 | 题号 | 答案 | 题号 | 答案 | 题号 | 答案 | |---|---|---|---|---|---| | 1 | B | 16 | A(正确) | 31 | B | | 2 | D | 17 | B(错误) | 32 | D | | 3 | C | 18 | A(正确) | 33 | C | | 4 | C | 19 | A | 34 | D | | 5 | B | 20 | C | 35 | B | | 6 | D | 21 | C | 36 | D | | 7 | D | 22 | A(正确) | 37 | B | | 8 | C | 23 | B(错误) | 38 | C | | 9 | B | 24 | B(错误) | 39 | B | | 10 | A | 25 | B | 40 | D | | 11 | A | 26 | A | 41 | C | | 12 | D | 27 | C | 42 | A | | 13 | C | 28 | B(错误) | 43 | D | | 14 | A | 29 | A(正确) | | | | 15 | B | 30 | A(正确) | | | 分值:第 1~15 题每题 2 分;第 16 题 1 分;第 17、18、22、23、24、28、29、30 题每题 1.5 分;其余每题 3 分,共 100 分。 ## 二、单项选择题(第 1~15 题) ### 第 1 题:精确存储大整数 **答案:B,`long long`。** 需要精确存储的整数是 $10^{18}+1$。 按竞赛中通常采用的类型规格,32 位有符号 `int` 的最大值为 $2^{31}-1=2147483647$,无法容纳这个数;64 位有符号 `long long` 的最大值为 $2^{63}-1\approx9.22\times10^{18}$,能够精确存储。 `float` 和 `double` 虽然可以表示数量级很大的数,但有效精度有限。常见的 IEEE 754 `double` 有 53 位二进制有效精度,能够连续精确表示的非负整数范围只到 $2^{53}$。在 $10^{18}$ 附近,相邻可表示数的间距是 128,不能精确表示 $10^{18}+1$;`float` 的精度更低。 **易错点:表示范围大,不等于能精确存储范围内的每一个整数。** ### 第 2 题:十六进制转八进制 **答案:D,1365。** 先转为十进制: $$ (2F5)_{16}=2\times16^2+15\times16+5=757。 $$ 再检查: $$ (1365)_8=1\times8^3+3\times8^2+6\times8+5=757。 $$ 也可以利用二进制中转:每个十六进制位转成 4 位二进制,再从右向左按 3 位分组。 ```text 2F5₁₆ = 0010 1111 0101₂ = 001 011 110 101₂ = 1 3 6 5₈ ``` ### 第 3 题:整数除法与取余 **答案:C,7。** `a=7`、`b=3`,整数除法 `7/3=2`,取余 `7%3=1`。 ```cpp a / b * b + a % b = 2 * 3 + 1 = 7 ``` 正整数除法满足:被除数 = 商 × 除数 + 余数。 ### 第 4 题:判断合法出栈序列 **答案:C,3、1、2、4。** 栈遵循“后进先出”。要先弹出 3,必须先依次压入 1、2、3。弹出 3 后,栈中从底到顶是 1、2,此时必须先弹出 2,才能弹出 1,因此不可能紧接着输出 1。 其他选项均可实现: - A:压入 1、2,弹出 2;压入 3、4,依次弹出 4、3、1。 - B:每压入一个数就立即弹出。 - D:压入并弹出 1;压入 2、3、4,再依次弹出 4、3、2。 ### 第 5 题:完全二叉树的叶子数 **答案:B,50。** 将完全二叉树的结点按层序编号为 1~100。编号为 $i$ 的结点,其左孩子编号为 $2i$。 当 $2i\le100$ 时有孩子,即编号 1~50 的结点不是叶子;编号 51~100 的结点都是叶子,共 50 个。 一般地,含 $N$ 个结点的完全二叉树叶子数为: $$ N-\left\lfloor\frac N2\right\rfloor=\left\lceil\frac N2\right\rceil。 $$ ### 第 6 题:3 或 5 的倍数之和 **答案:D,2418。** 条件中的 `||` 表示满足任意一个条件即可,因此应求 1~100 中所有 3 或 5 的倍数之和。 3 的倍数之和: $$ 3(1+2+\cdots+33)=3\times\frac{33\times34}{2}=1683。 $$ 5 的倍数之和: $$ 5(1+2+\cdots+20)=1050。 $$ 15 的倍数被重复计算,需要减去: $$ 15(1+2+\cdots+6)=315。 $$ 最终结果为 $1683+1050-315=2418$。这就是容斥原理。 ### 第 7 题:每次上 1、2、3 级台阶 **答案:D,81。** 设 $f(i)$ 为到达第 $i$ 级台阶的方法数。按照最后一步走了几级分类: $$ f(i)=f(i-1)+f(i-2)+f(i-3)。 $$ 边界为 $f(0)=1$,表示不走也是到达地面的一种方式;也可以直接从 $f(1)=1,f(2)=2,f(3)=4$ 开始推。 | 台阶数 $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |---|---|---|---|---|---|---|---|---|---| | 方法数 $f(i)$ | 1 | 1 | 2 | 4 | 7 | 13 | 24 | 44 | 81 | ### 第 8 题:BFS 的入队次序 **答案:C,14 个。** 坐标按 `(行,列)` 表示,起点为 `(0,0)`,终点为 `(3,4)`。必须同时遵守“队首先出”和邻居按“上、下、左、右”检查。 首次入队的完整顺序,直到 E 入队为止: | 入队编号 | 格子坐标 | 由哪个格子发现 | |---|---|---| | 1 | (0,0),S | 初始入队 | | 2 | (1,0) | (0,0) | | 3 | (0,1) | (0,0) | | 4 | (2,0) | (1,0) | | 5 | (1,1) | (1,0) | | 6 | (0,2) | (0,1) | | 7 | (2,1) | (2,0) | | 8 | (1,2) | (1,1) | | 9 | (2,2) | (2,1) | | 10 | (3,2) | (2,2) | | 11 | (4,2) | (3,2) | | 12 | (3,3) | (3,2) | | 13 | (4,1) | (4,2) | | 14 | (3,4),E | (3,3) | 关键细节:处理 `(3,2)` 时,先发现下面的 `(4,2)`,再发现右边的 `(3,3)`。所以在 `(3,3)` 发现 E 之前,`(4,2)` 已经出队,并使 `(4,1)` 入队。 **易错点:题目统计累计入队过多少格,不是当前队列长度,也不是最短路径上的格子数。** ### 第 9 题:最大公约数条件 **答案:B,6 个。** 因为 $\gcd(n,60)=6$,所以令 $n=6t$,则: $$ \gcd(6t,60)=6\gcd(t,10)=6, $$ 即 $\gcd(t,10)=1$。由 $1\le n\le100$,可得 $1\le t\le16$。 在 1~16 中排除 2 或 5 的倍数,剩下: $$ t=1,3,7,9,11,13。 $$ 对应 $n=6,18,42,54,66,78$,共 6 个。 ### 第 10 题:最少硬币数 **答案:A,3 枚。** 可以用 $4+4+1=9$,因此最多需要 3 枚。 1 枚显然不行;2 枚可能组成的金额为 $2,5,7,8,10,12$,没有 9,因此最少为 3 枚。 **易错点:每次选最大面值的贪心策略在这里不成立。**按该策略会选 $6+1+1+1$,需要 4 枚。 ### 第 11 题:指针与数组下标 **答案:A,`14,13`。** 初始数组为 `[1,3,5,7,9]`,`p=a+2`,即 `p` 指向 `a[2]`。 第一条赋值: ```cpp *(p - 1) = p[0] + p[2]; ``` 等价于 `a[1]=a[2]+a[4]=5+9=14`。 第二条赋值: ```cpp p[1] = *(a + 1) - a[0]; ``` 等价于 `a[3]=a[1]-a[0]=14-1=13`。注意这里的 `a[1]` 已被上一条语句修改。 所以输出 `14,13`。 ### 第 12 题:二分查找的最坏比较次数 **答案:D,10 次。** 每比较一个中间元素,就将候选区间缩小到大约原来的一半。按照题目通常的计数方式,一次与中间元素的比较算一次: $$ \lfloor\log_2 1000\rfloor+1=10。 $$ 也可以观察最坏情况下的非空候选区间长度: ```text 1000 → 500 → 250 → 125 → 62 → 31 → 15 → 7 → 3 → 1 ``` 共需要检查 10 个中间元素。注意题目不是统计具体代码里 `==`、`<` 等运算符的执行总次数。 ### 第 13 题:由前缀和求原数组 **答案:C,58。** 已知 $S[i]=3i^2+i$,利用: $$ a[i]=S[i]-S[i-1]。 $$ 因此: $$ a[10]=(3\times10^2+10)-(3\times9^2+9)=310-252=58。 $$ 不要把 `S[10]` 直接当成 `a[10]`:前者是前十项之和。 ### 第 14 题:到所有点的距离和最小 **答案:A,37。** 数轴上到各点距离之和,在中位数处取得最小值。七个点已经按从小到大排序,其中位数为第 4 个数 7。 取 $P=7$: $$ 6+4+3+0+3+8+13=37。 $$ 也可以配对证明:对 `(1,20)`、`(3,15)`、`(4,10)`,距离和分别至少为 19、12、6;`P=7` 同时达到这三个下界,且到中间点 7 的距离为 0,所以最小值为 $19+12+6=37$。 ### 第 15 题:度数和与边数 **答案:B,18。** 无向图中每条边给两个端点各贡献 1 的度数,因此度数总和等于边数的 2 倍。 $$ 2|E|=4\times3+(10-4)\times4=36,\qquad |E|=18。 $$ ## 三、阅读程序(第 16~33 题) ### 第一组:除以 2 与二进制数位(第 16~21 题) #### 程序整体含义 初始 `x=1,y=1`。每轮循环无论奇偶,`x` 都增加 1;只有当前 `n` 为奇数时,`y` 才增加 1。随后执行 `n/=2`。 对非负整数而言,反复执行 `n%2`、`n/=2`,相当于从低位到高位依次读取二进制数位。 - `x` = 1 + 循环次数。对正数,这就是 1 + 二进制位数。 - `y` = 1 + 原数二进制中 1 的个数。 - 输入 0 时不进入循环,输出 `1 1`。 #### 第 16 题 **答案:A,正确。** 输入 3,二进制为 `11`。 | 时刻 | 本轮开始的 n | 操作后的 x | 操作后的 y | n/=2 后 | |---|---|---|---|---| | 初始 | 3 | 1 | 1 | — | | 第 1 轮 | 3,奇数 | 2 | 2 | 1 | | 第 2 轮 | 1,奇数 | 3 | 3 | 0 | 最终输出 `3 3`。 #### 第 17 题 **答案:B,错误。** 删除奇数分支中的 `++x` 后,`x` 只在处理二进制位 0 时增加,`y` 只在处理二进制位 1 时增加。0 的数量与 1 的数量不一定相等。 例如输入 `1`,唯一一轮是奇数分支,只增加 `y`,输出为 `1 2`,两个数不相等。 #### 第 18 题 **答案:A,正确。** 原程序每轮一定增加 `x`,增加 `y` 时也一定增加 `x`,而二者初始相等,所以始终有 `x>=y`。 从二进制角度看,1 的数量不可能超过总位数。输入 0 时二者均为 1,结论也成立。 #### 第 19 题 **答案:A,陷入死循环。** 当条件改为 `while(n>=0)`,`n` 变成 0 后仍满足循环条件,而 `0/2` 永远是 0,无法退出。 按题目算法层面的讨论,应选死循环。严格按 C++ 语言语义,若无限运行使有符号变量 `x` 溢出,还会涉及未定义行为,但这不改变本题要求识别的循环条件错误。 #### 第 20 题 **答案:C,`4 3`。** 输入 6,二进制为 `110`,共有 3 位,其中有两个 1。所以 `x=1+3=4`,`y=1+2=3`。 逐轮也可得到: ```text 初始:n=6,x=1,y=1 第1轮:6为偶数,x=2,y=1,n=3 第2轮:3为奇数,x=3,y=2,n=1 第3轮:1为奇数,x=4,y=3,n=0 ``` #### 第 21 题 **答案:C,31 次。** 第二个数恰好为 2,要求原数二进制中恰好有一个 1,即原数为 2 的非负整数次幂。 在 $0\le n\le2^{31}-1$ 范围内,这些数是: $$ 2^0,2^1,\ldots,2^{30}, $$ 共 31 个。0 的输出第二个数为 1,不符合条件;$2^{31}$ 超出范围。 ### 第二组:高精度加法及输出细节(第 22~27 题) #### 程序整体含义 输入字符串逆序保存到数组中:`a[0]`、`b[0]` 是个位,`a[1]`、`b[1]` 是十位,以此类推。数组为全局变量,未赋值的位置初始为 0。 令 `L=max(a_len,b_len)`。程序从 `i=0` 算到 `i=L`,多计算一位以容纳最高位进位,然后固定输出 `c[L]` 到 `c[0]`。 **原程序总是输出 L+1 个字符,不会主动去除前导零。**正常加法中每个 `c[i]` 都在 0~9 之间。 #### 第 22 题 **答案:A,正确。** `123+456=579`,但 `L=3`,程序输出 `c[3],c[2],c[1],c[0]`。最高位 `c[3]=0`,所以输出 `0579`。 #### 第 23 题 **答案:B,错误。** 即使输入没有前导零,只要最高位没有产生进位,多输出的那一位仍然是 0。例如 `123 456` 的输出就是 `0579`,足以否定“一定不会含有前导零”。 #### 第 24 题 **答案:B,错误。** 删除 `+carry[i]` 会使进位无法参与下一位的计算,但结果不一定严格变小。 例如输入 `1 2`,本来就没有进位,修改前后都输出 `03`,结果相同。因此“一定比原来的结果小”不成立。 #### 第 25 题 **答案:B,`013023`。** 计算 $12345+678=13023$,原输入最大长度为 5,所以输出 6 位,最前面补出一个 0。 | 数位 | 两个数的本位及进位 | 本位结果 | 向高位进位 | |---|---|---|---| | 个位 | 5+8+0=13 | 3 | 1 | | 十位 | 4+7+1=12 | 2 | 1 | | 百位 | 3+6+1=10 | 0 | 1 | | 千位 | 2+0+1=3 | 3 | 0 | | 万位 | 1+0+0=1 | 1 | 0 | | 额外最高位 | 0+0+0=0 | 0 | 0 | 从高位到低位输出为 `013023`。 #### 第 26 题 **答案:A,`01010`。** 将进位条件改为 `c[i]>10` 后,恰好等于 10 时不会进位,也不会减去 10。 对输入 `95 15`: | i | 计算结果 | 是否大于 10 | 最终 c[i] | 下一位收到的进位 | |---|---|---|---|---| | 0 | 5+5+0=10 | 否 | 10 | 0 | | 1 | 9+1+0=10 | 否 | 10 | 0 | | 2 | 0+0+0=0 | 否 | 0 | 0 | 输出顺序是 `0`、`10`、`10`,直接连接后为 `01010`。 **易错点:数组元素是整数,不保证始终只有一位。`cout << 10` 会输出两个字符。** #### 第 27 题 **答案:C,长度一定为 n+1,且第一个字符为 `'0'`。** 两个输入均为 n 位数,所以 `L=n`,原程序固定输出 n+1 位。由于和小于 $10^n$,不存在进入第 n 位的进位,因此 `c[n]=0`,输出的第一个字符一定为 `'0'`。 ### 第三组:质数判断与深度优先搜索(第 28~33 题) #### 程序整体含义 `check_prime(x)` 判断 x 是否为质数。若 x 是合数,则一定有一个不超过 $\sqrt{x}$ 的因子,因此试除到 `i*i<=x` 即可。 `search_result(x)` 的逻辑为: 1. 如果 x 不是质数,立即停止这条搜索分支。 2. 如果 x 是质数且 `x>=n`,输出 x,并停止向后添加数字。 3. 如果 x 是质数且 `x=n`,所以该调用会直接输出 5。 #### 第 30 题 **答案:A,正确。** 把添加数字范围改为 `1,3,5,7,9`,只是跳过了末位为偶数的扩展。 扩展得到的数至少有两位,如果末位是 0、2、4、6、8,则它是大于 2 的偶数,必定不是质数。在原程序中,这些分支也会立即返回,既不会输出,也不会继续扩展。 因此删除这些无效分支后,输出内容和顺序都不变。 #### 第 31 题 **答案:B,29。** 输入 24 时,先从首位 2 开始搜索: - 20、21、22 不是质数,返回。 - 23 是质数,但小于 24,所以继续扩展。 - 230~239 中只有 233、239 是质数,依次输出这两个数。 - 返回两位数这一层,24~28 均非质数,随后访问 29。 - 29 是质数且不小于 24,因此第 3 行输出 29。 前几行是: ```text 233 239 29 31 37 ... ``` **易错点:深度优先会先完成 23 的整条分支,再访问 29。** #### 第 32 题 **答案:D。** 对于输出的任意一个至少两位的数 x,它必然由上一层 `search_result(x/10)` 添加末位得到。上一层只有通过质数检查,才能继续递归。因此删去 x 的末位后得到的数必定是质数。 其余选项: - A 错:输入 24 时,233、239 出现在 29 前面,并非从小到大。 - B 错:输入 23 时输出 9 行,输入 24 时输出 10 行;原来输出的 23 被两个后代 233、239 替代,行数增加。 - C 错:例如输入 10 时会输出 29、31、59、71、79,个位并不只可能是 3 或 7。 #### 第 33 题 **答案:C,14 行。** 输入 200 时,一位和两位的质数前缀都小于阈值。可继续扩展的两位前缀就是第 28 题列出的 9 个数。 | 两位质数前缀 | 添加一位后仍为质数的数 | 数量 | |---|---|---| | 23 | 233、239 | 2 | | 29 | 293 | 1 | | 31 | 311、313、317 | 3 | | 37 | 373、379 | 2 | | 53 | 无 | 0 | | 59 | 593、599 | 2 | | 71 | 719 | 1 | | 73 | 733、739 | 2 | | 79 | 797 | 1 | 这些三位数均不小于 200,所以输出后停止扩展。总行数为 $2+1+3+2+0+2+1+2+1=14$。 ## 四、完善程序(第 34~43 题) ### 第一组:将 mn 进制转换成 n 进制(第 34~38 题) #### 1. 数组究竟表示什么 这里 $mn$ 表示 $m\times n$。数组 b 以低位在前的顺序,保存已读入部分所代表数值的 n 进制表示: $$ V=b[0]+b[1]n+b[2]n^2+\cdots+b[len-1]n^{len-1}。 $$ `b[0]` 是个位,`b[len-1]` 是最高位。每轮进位结束后,各位都在 0~n-1 之间;进位前可以暂时超出这个范围。 #### 2. 每读入一位,要执行哪种运算 从高到低读入原数的新数位 x,相当于: $$ V_{新}=V_{旧}\times(mn)+x。 $$ 这与读十进制数时“旧数乘 10 再加新数字”是同一个道理。**当前用 n 进制保存数值,不会改变原输入的进制是 mn 这一事实。** #### 第 34 题:① **答案:D,`b[j - 1] * m`。** ```cpp for (int j = len; j >= 1; j--) b[j] = b[j - 1] * m; ``` 这行同时做了两件事:将数字从第 j-1 位放到第 j 位,并将数字本身乘以 m。 假设旧数位是 c,它原来代表 $c n^{j-1}$,现在代表: $$ (cm)n^j=(c n^{j-1})\times mn。 $$ **为什么不能再写 `* n`?**因为位置从 j-1 变成 j,位权已经从 $n^{j-1}$ 变成 $n^j$,相当于自动乘了一次 n。就像十进制的 3 从个位移到十位,数字仍为 3,但代表的数值已经是 30。 若写成 `b[j]=b[j-1]*m*n`,结合位置变化,实际乘的是 $mn^2$,多乘了一次 n。 **为什么倒序移动?**从高位向低位赋值,能够保留还没被读取的旧数位;若从低位往高位操作,会提前覆盖后面需要使用的数据。 #### 第 35 题:② **答案:B,`x`。** 整体高移一位后,最低位空出,相当于为 0。将新读入的 x 加到最低位,直接写: ```cpp b[0] = x; ``` x 可能大于等于 n,但后面的循环会统一处理进位。 #### 第 36 题:③ **答案:D,`b[j] / n`。** n 进制中,每满 n 就向高一位进 1,所以需要向高一位增加 `b[j]/n`: ```cpp b[j + 1] += b[j] / n; ``` 必须使用 `+=`,因为高一位可能已有数值,进位应该在原有值上累加。 #### 第 37 题:④ **答案:B,`b[j] % n`。** 进位后,本位留下除以 n 的余数: ```cpp b[j] = b[j] % n; ``` 商送到高位,余数留在本位。进位循环由低位向高位进行,能继续处理进位引起的连锁变化。 如果向当前最高位之外又进了一位,`len++` 会让循环继续处理这个新数位。 #### 第 38 题:⑤ **答案:C,`len > 1 && b[len - 1] == 0`。** ```cpp while (len > 1 && b[len - 1] == 0) len--; ``` 每次读入都先增加 len,但实际数值未必需要这么多位,因此最后需要删除最高位多余的零。 - 应检查 `b[len-1]`,因为它是最高位;`b[0]` 是最低位,末尾的零不能随意删除。 - 必须限制 `len>1`,保证 A=0 时仍保留一位 0。如果使用 `len>0`,可能把唯一的一位零也删掉,导致没有输出。 #### 3. 完整示例 取 n=2、m=3,输入的原数是六进制 `1 4`: $$ (14)_6=1\times6+4=10=(1010)_2。 $$ 读完首位 1 后,有效数位是 `[1]`。实际数组可能还带有最高位的零,这些零不影响数值,最后会统一去除。 读入下一位 4,移动并乘以 m,再写入最低位,得到尚未进位的有效部分 `[4,3]`,表示 $4+3\times2=10$。 ```text [4,3] 第0位为4,向高位进2,本位留0 [0,5] 第1位为5,向高位进2,本位留1 [0,1,2] 第2位为2,向高位进1,本位留0 [0,1,0,1] 所有数位均合法 ``` 数组低位在前,倒序输出就是 `1 0 1 0`。 #### 4. 补全后的完整程序 ```cpp #include constexpr int N = 100005; long long b[N]; int main() { long long n, m, d; std::cin >> n >> m >> d; int len = 1; for (int i = 0; i < d; i++) { long long x; std::cin >> x; for (int j = len; j >= 1; j--) b[j] = b[j - 1] * m; b[0] = x; len++; for (int j = 0; j < len; j++) { if (b[j] >= n) { b[j + 1] += b[j] / n; b[j] %= n; if (j + 1 == len) len++; } } } while (len > 1 && b[len - 1] == 0) len--; for (int i = len - 1; i >= 0; i--) std::cout << b[i] << ' '; return 0; } ``` 理解程序时,抓住“读入一位 → 旧数乘 mn 并加 x → 按 n 进制整理进位”。题干虽然称为“逐位除以 n”,但整个读入过程用上述递推式来解释更清楚。 ### 第二组:枚举连续分段,使平均值极差最小(第 39~43 题) #### 1. 题意与递归状态 字符串中的每个十六进制字符分别代表一个整数。例如 `016A` 对应 `[0,1,6,10]`,不是把整串当成一个十六进制整数参与运算。 在 n-1 个内部间隙中选择至少一个切分位置,将序列分成至少两段。对每段求平均值,目标是让“最大段平均值 - 最小段平均值”尽可能小。 函数: ```cpp split(int l, int cnt, double mnb, double mxb) ``` 各参数含义如下: | 参数 | 含义 | |---|---| | l | 下一段从第 l 个数开始 | | cnt | 已选的内部切分位置数量,即切了几刀 | | mnb | 已确定各段平均值的最小值 | | mxb | 已确定各段平均值的最大值 | 每次枚举当前段的右端点 r,当前段为 `[l,r]`,然后递归处理从 r+1 开始的剩余部分。 当 `l>n` 时,所有数已经分完。只有 `cnt>0` 才满足至少切一刀的要求;此时用 `mxb-mnb` 更新最优答案。 #### 第 39 题:①,十六进制字符转数值 **答案:B,`c - (c < 'A' ? '0' : 'A' - 10)`。** 在题设 ASCII 编码和字符范围下: - `'0'`~`'9'` 均小于 `'A'`,用 `c-'0'` 得到 0~9。 - `'A'`~`'F'` 用 `c-'A'+10` 得到 10~15。 选项 B 中,字母分支是: ```cpp c - ('A' - 10) // 等价于 c - 'A' + 10 ``` 例如 `'A'` 得到 10,`'F'` 得到 15。 其他选项:A 使用 `c<'9'`,会把字符 `'9'` 错分到字母分支;C 把两个分支颠倒;D 将字母分支中的加 10 错写成减 10。 #### 第 40 题:②,枚举当前段的右端点 **答案:D,`int r = l; r <= n; ++r`。** 段 `[l,r]` 至少包含一个元素,因此最小右端点为 l;最后一段必须能包含第 n 个元素,所以最大右端点为 n。每个位置都可能是右端点,必须逐个枚举。 - A 从 l+1 开始,会漏掉长度为 1 的段。 - B 限制 r0` 的检查,而唯一一段的最大和最小平均值相等,极差为 0,会让程序错误地对所有输入都得到 0。 例如输入 `2 09`,唯一合法方案是 `[0] | [9]`,正确极差为 9;不切分时极差为 0,但这不符合题意。 #### 第 43 题:⑤,递归初始状态 **答案:D,`1, 0, 1e100, -1e100`。** ```cpp split(1, 0, 1e100, -1e100); ``` - 数组从下标 1 开始保存数据,所以 l=1。 - 尚未选择内部切分位置,所以 cnt=0。 - 最小值初始设为很大的数,这样遇到第一个平均值时,`min` 会选中它。 - 最大值初始设为很小的数,这样第一次 `max` 也会选中真实平均值。 实际平均值在 0~15 之间,所以这两个初值足以充当正、负无穷的替代值。 #### 2. 手算示例:016A 数值序列为 `[0,1,6,10]`,共有 3 个内部间隙,至少切一刀,所以合法方案共 $2^3-1=7$ 个。 | 分段方案 | 各段平均值 | 极差 | |---|---|---| | `[0] \| [1,6,10]` | 0、17/3 | 17/3 | | `[0,1] \| [6,10]` | 1/2、8 | 15/2 | | `[0,1,6] \| [10]` | 7/3、10 | 23/3 | | `[0] \| [1] \| [6,10]` | 0、1、8 | 8 | | `[0] \| [1,6] \| [10]` | 0、7/2、10 | 10 | | `[0,1] \| [6] \| [10]` | 1/2、6、10 | 19/2 | | `[0] \| [1] \| [6] \| [10]` | 0、1、6、10 | 10 | 最小极差为 $17/3$,保留六位小数输出 `5.666667`。 #### 3. 补全后的完整程序 ```cpp #include #include #include using namespace std; constexpr int N = 25; int n, a[N]; char s[N]; double ans = 1e100; int value(char c) { return c - (c < 'A' ? '0' : 'A' - 10); } void split(int l, int cnt, double mnb, double mxb) { if (l > n) { if (cnt == 0) return; ans = min(ans, mxb - mnb); return; } int sum = 0; for (int r = l; r <= n; ++r) { sum += a[r]; double nwb = sum * 1.0 / (r - l + 1); split(r + 1, cnt + (r < n), min(mnb, nwb), max(mxb, nwb)); } } int main() { cin >> n >> (s + 1); for (int i = 1; i <= n; ++i) a[i] = value(s[i]); split(1, 0, 1e100, -1e100); cout << fixed << setprecision(6) << ans; return 0; } ``` 代码按常见竞赛 C++14/C++17 环境书写;读取 `s+1` 的语句含义与原题一致。原文件在输出语句处结束了代码展示,这里补齐了 `return 0;` 和主函数结束括号。 #### 4. 为什么枚举不会漏解 任意一个合法连续分段方案,都唯一确定第一段右端点、第二段右端点,直到最后一段右端点 n。递归在每层遍历所有可能的右端点,因此每个方案都能被访问一次,也不会重复。 n-1 个内部间隙各有“切”或“不切”两种选择,共 $2^{n-1}$ 个完整方案,排除一刀不切后为 $2^{n-1}-1$ 个合法方案。代码通过累计 sum 和维护最值,使每条递归边的工作量为常数,总时间复杂度为 $O(2^n)$,递归栈空间为 $O(n)$。n≤20 时适合直接枚举。 ## 五、复习重点 | 知识点 | 对应题号 | 需要掌握的结论或方法 | |---|---|---| | 整数范围与浮点精度 | 1 | 存得下某数量级,不代表能精确表示每个整数 | | 进制与数位权重 | 2、16~21、34~38 | 除 2 读取二进制位;移动数位改变位权 | | 栈、树、图 | 4、5、8、15 | 后进先出、完全二叉树编号、BFS 队列顺序、握手定理 | | 递推与计数 | 6、7、9 | 容斥、按最后一步分类、最大公约数化简 | | 最优性判断 | 10、14 | 贪心需要依据;距离和最小取中位数 | | 数组与指针 | 11 | 指针偏移对应数组下标,注意赋值顺序 | | 二分与前缀和 | 12、13 | 区间反复减半;原项等于相邻前缀和之差 | | 高精度加法 | 22~27 | 低位先算、进位条件、固定输出长度 | | DFS 与剪枝 | 28~33 | 区分递归访问顺序和数值顺序,跟踪提前返回 | | 连续分段枚举 | 39~43 | 状态含义、内部切分计数、浮点除法、最值初值 | 做程序题时,先确定变量含义与循环不变量,再手算一个小例子;看到“一定”“所有”等断言时,优先检查 0、最小输入、无进位和边界恰好相等等情形。