简介:这份2024年信息学奥赛CSP-J初赛真题详细分析面向备战CSP-J的中学生、少儿编程学习者及C++入门教师,围绕初赛真题提供答案与逐题解析,帮助读者系统梳理考点、定位薄弱环节并安排考前复盘。压缩包共1个docx文档,大小约121KB,以文字版试题与解析为主体,结构紧凑,便于打印批注、按题型检索或在课堂上拆解讲解。内容覆盖32位int存储范围、二进制与十六进制转换、存储单位换算、格雷码、排列组合、二分查找、栈与队列、无向图度数及二叉树遍历,并延伸到C++基本数据类型、循环语句和操作系统常识,解析中给出选项判断、计算过程与易错点提示。现有2594人学习,适合用于自测模拟、错题归因和知识点串联,也能为教练整理专题复习材料提供参考。
1. 一张卷子看清 CSP-J 初赛:30 + 40 + 30 的分数地图
很多人对 2024 年信息学奥赛 CSP-J 初赛真题的第一印象是「背概念就能过」,真把 42 道题从头翻到尾会发现恰好相反:决定能不能进复赛的分水岭在阅读程序和完善程序,这两块占了 70 分,考的不是记忆,而是把一段陌生 C++ 代码读成一句人话的能力。整张卷子的骨架非常清楚:单选 15 题每题 2 分共 30 分,铺开计算机常识、进制编码、组合计数、数据结构四条线;阅读程序 3 段共 40 分,判断题 1.5 分、选择题 3 分;完善程序 2 段共 10 空、每空 3 分共 30 分。少儿编程阶段学过的语法只是入场券,真正拉开差距的是「读到边界条件会不会停一下」「看到 return 会不会分清问的是返回值还是输出」。这份梳理按分数块拆开,每块给出考点、答案、典型陷阱和可直接抄用的模板。
2. 单项选择题:15 道题背后的四类考点簇
单选看着零散,其实很集中:计算机与语言常识 6 题、进制与编码 2 题、组合计数 2 题、数据结构 5 题。先把答案摆出来,再逐簇讲为什么。
| 题号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 答案 | C | A | B | D | D | C | D | B | B | A | B | A | D | A | B |
2.1 计算机与语言常识:int 范围、存储单位、类型与循环
这一簇共 6 题,全是「定义级」知识,错一道就是 2 分,而且错法高度雷同。
第 1 题问 32 位 int 的存储范围,正确答案是 -2147483648 ~ +2147483647。最普遍的错法是选 D,认为正负对称。原因是把「补码表示」当成了「符号位 + 绝对值」:补码里 0 只占一种编码,多出来的一个编码位让负数端多表示一个数,所以负端比正端多 1。记住一句话就够:n 位有符号整数的范围是 −2^(n−1) 到 2^(n−1)−1,两端不对称。
第 5 题问 1MB 是多少二进制位,答案 8388608。这道题的失分点不在计算,在单位链没走完:1MB = 1024KB,1KB = 1024B,1B = 8bit,三次连乘 1024×1024×8。很多人算到 1048576 就停了,那是字节数不是位数。位(bit)、字节(Byte)、字(Word)三者的关系要一次性记死,别在考场上现推。
第 6、7 题是一对:struct 属于复合数据类型,不是基本数据类型;C++ 的循环只有 for、while、do-while 三种,repeat-until 是别的语言里的写法。第 15 题问编译器的作用,选「将源代码转换为机器代码」,干扰项「直接执行源代码」是解释器干的事。第 10 题问哪个不是操作系统,Notepad 是文本处理软件。这三题合起来说明一件事:C++ 语言常识和操作系统常识是两套知识,不要混着记。
提示:这类题的正确率靠平时「一条定义配一个反例」记,比如「do-while 至少执行一次」「struct 可以嵌 struct 但不能当基本类型用」。
2.2 进制与编码:混合进制运算和格雷码的相邻一位
第 2 题是混合进制计算:(14₈ − 1010₂) × D₁₆ − 1101₂。做法只有一条:全部转成十进制再算。14₈ = 12,1010₂ = 10,D₁₆ = 13,1101₂ = 13,于是 (12 − 10) × 13 − 13 = 13,选 A。这里的翻车点不在算术,在识标:下标 8、2、16 混在一行里,眼睛一花就把 14₈ 当成十进制 148,后面全错。养成习惯,读题时先把每个数的进制圈出来再动笔。
第 4 题考 4 位格雷码序列,答案是 D。格雷码的核心性质只有两条:相邻两个码字只有一位不同;首尾两个码字也只有一位不同(循环格雷码)。判定时不用逐个数字推,直接检查每个相邻对儿的异或结果是不是 2 的幂,或者干脆用构造公式 g = b ^ (b >> 1)。
| 十进制 | 二进制 b | 格雷码 g = b ^ (b>>1) |
|---|---|---|
| 0 | 000 | 0000 |
| 1 | 001 | 0001 |
| 2 | 010 | 0011 |
| 3 | 011 | 0010 |
| 4 | 100 | 0110 |
| 5 | 101 | 0111 |
| 6 | 110 | 0101 |
| 7 | 111 | 0100 |
对照表格看选项:B 的 0111 到 0100 差了两位,C 的 0010 到 0100 差了三位,A 的 0101 到 1000 差了四位,只有 D 全程相邻一位。会写这个公式,比背序列稳得多。
2.3 组合计数:加法原理分类与捆绑法
第 3 题是「10 人分 3 个部门,选 4 人且每部门至少 1 人」。破题点是一句判断:4 个人分给 3 个部门且每部门非空,必然有一个部门出 2 人,另外两个各出 1 人。于是按「哪个部门出 2 人」分类,三类互斥,用加法原理合并:
- A 部门 2 人:C(4,2) × C(3,1) × C(3,1) = 6 × 3 × 3 = 54
- B 部门 2 人:C(4,1) × C(3,2) × C(3,1) = 4 × 3 × 3 = 36
- C 部门 2 人:C(4,1) × C(3,1) × C(3,2) = 4 × 3 × 3 = 36
合计 126。这里最容易犯的错是「先随便选 4 人再减掉不合法」,容斥方向没错但容易漏项;更常见的错是分类时三个部门用同一套系数,忘了每个部门人数不同、组合数也不一样。
第 14 题是「5 男 3 女站一排,3 女必须相邻」,标准捆绑法:把 3 个女生看成一个整体,与 5 个男生共 6 个对象全排列 6! = 720,再乘女生内部 3! = 6,得 4320。两题合起来是一条方法论:相邻问题捆绑、至少一个用分类,先看「有没有一个必然成立的强约束」,抓住它就能把问题降一维。
2.4 数据结构:二分次数、握手定理、遍历还原与栈序判定
这一簇 5 题,是单选里最像「算法题」的部分。
第 9 题问 1000 个元素二分查找最多比较几次,答案 10。公式是 ⌈log₂n⌉,因为 2⁹ = 512 < 1000 ≤ 1024 = 2¹⁰。错选 25 的是把「最多比较次数」当成了「最多访问元素数除以二」,错选 7 的是记成了 log₂100 = 6.6 向下取整。这类题一律向上取整,因为 1000 不可能在 9 次内收敛完。
第 11 题考无向图所有顶点度数之和,等于边数的两倍,因为一条边给两个端点各贡献 1 度。对应的有向图结论是:所有顶点入度之和 = 所有顶点出度之和 = 边数。两个结论一起记,别只记一半。
第 12 题给前序 [A,B,D,E,C,F,G]、中序 [D,B,E,A,F,C,G] 求后序。还原步骤固定:前序第一个 A 是根,中序在 A 处切分,左边 DBE 是左子树、右边 FCG 是右子树;左子树前序 BDE 得根 B,中序 D B E 得左 D 右 E;右子树同理得根 C、左 F 右 G。后序按「左、右、根」拼回去:DEB + FGC + A = DEBFGCA,选 A。
第 13 题是栈的出栈序列判定,输入依次入栈 1 到 6,问哪个出栈顺序不可能,答案 D(1 3 5 2 4 6)。这里给两条判定路径,第一条是硬模拟:
// 判断给定出栈序列是否可能,n 为元素个数,元素按 1..n 顺序入栈 bool possible(const vector<int>& s, int n) { stack<int> st; int nxt = 1; // 下一个待入栈的元素 for (int x : s) { while (nxt <= x) st.push(nxt++); // 把 x 及之前的元素全部压进去 if (st.empty() || st.top() != x) // 栈顶不是 x,说明它被压在下面了 return false; st.pop(); } return true; }nxt是入栈指针,while保证要出栈的元素已经在栈里;一旦栈顶不是它,就说明它上面还压着别人,顺序不成立。第二条是口算规则:对出栈序列里的任意元素,它后面所有比它小的元素必须按从大到小的顺序出现。拿 D 验证,元素 5 后面比它小的有 2 和 4,出现顺序是 2 然后 4,升序,违规。这条规则在组合数学里叫避开 312 模式,考场上不用记名字,记「后面的小数必须降序」就够用。
3. 阅读程序:三种骨架,三套读法
阅读程序 40 分,是全卷最值钱也最容易连错的地方。三段程序分别是质数统计、一维 DP、递归快速幂,每段配了判断和选择,答案先列出来:
| 题号 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 答案 | T | F | T | B | A | T | F | F | A | B | A | F | T | T | B | C | D |
3.1 第一段:质数统计,读循环边界就是读整段程序
这段程序三个函数,isPrime判素、countPrimes计数、sumPrimes求和,主函数输入 x 后输出「个数 和」。整段的命门全在for (int i = 2; i * i <= n; i++)这一行。
第 16 题输入 10,10 以内素数是 2、3、5、7,个数 4、和 17,判断为真。第 19 题输入 50,求和得 328,选 B,这个只能老老实实加,但可以先列素数再累加,2、3、5、7、11、13、17、19、23、29、31、37、41、43、47,一共 15 个。第 18 题问sumPrimes是不是求 2 到 n 的素数和,判断为真,这题是送分的语义理解。
真正有区分度的是第 17 题和第 20 题。第 17 题把条件改成i <= n/2,问输入 20 时countPrimes(20)是否变成 6,答案是假,仍然是 8。为什么?因为n/2对 n ≥ 4 来说远大于 √n,循环次数变多但判定结果不变;唯一的边界是 n = 2,此时 n/2 = 1,循环一次都不进,直接返回真,2 依然被算作素数。很多人只想到「循环次数变了」就判真,忽略了结果集没变。第 20 题把i * i <= n改成i <= n,输入 10 时输出会怎样,选 A「不能正确计算」。原因很直接:当 i 走到 n 时,n % n == 0成立,函数返回假,于是每个数都被判成合数,输出变成 0 和 0。这道题的设计意图就是让你体会i * i <= n不是优化技巧,是正确性的一部分。
把这段的模板抄下来,注意边界和溢出:
bool isPrime(int n) { if (n < 2) return false; // 0、1、负数都不是素数 for (int i = 2; (long long)i * i <= n; ++i) { // 强转 long long 防 i*i 溢出 if (n % i == 0) return false; // 找到因子立刻退出 } return true; }(long long)i * i这个强转在 n 接近 int 上限时是必需的,否则 i*i 先溢出成负数,循环条件直接失效。整段的时间复杂度是 O(n√n),countPrimes和sumPrimes各跑一遍,实际是两倍常数,但这不影响答案。
3.2 第二段:一维 DP,盯住下标错位和答案位置
第二段是最小花费爬楼梯模型,转移式在题干里给了:dp[i] = min(dp[i-1], dp[i-2]) + cost[i-1]。读这类程序只需要回答四个问题:状态是什么、从哪转移来、边界怎么设、答案取哪个。
状态dp[i]表示走到第 i 级的最小花费;转移来自前一级和前两级取小;边界dp[0] = dp[1] = 0,表示起点不花钱;答案在dp[n]。这里有一个非常隐蔽的点:转移式里的cost[i-1]与dp[i]的下标错开了一位,因为 cost 数组是从 0 开始存的。第 22 题就是拿这个做文章,把dp[i-1]改成dp[i-3],问会不会编译错误,答案是假——下标越界在 C++ 里不是编译期错误,数组下标运算合法,编译器不会拦,只会在运行时读到垃圾值或越界访问。这个点每年都有人错,因为直觉上「下标写错 = 编译报错」,实际上编译期只检查语法和类型,不检查下标范围。
具体数值题按递推算即可:第 21 题 cost 为 {10,15,20} 时输出 15,第 24 题那串 {1,100,1,1,1,100,1,1,100,1} 输出 6,第 25 题 {10,15,30,5,5,10,20} 输出 30。第 23 题说「程序总是输出 cost 数组中最小的元素」,判断为假,因为 DP 求的是路径最小和,不是单点最小值,这两者在数据偶然相等时容易误判,但语义完全不同。
第 26 题是「改一行」型,把转移式改成dp[i-1] + cost[i-2],输入 {5,10,15} 输出 10。这类题没有捷径,只能按改后的式子重新推一遍,千万别拿原答案套。改一行题的通用做法是:先在纸上写出改后的完整递推表,再逐项填,填三行就能看出规律,比在脑子里空转可靠。
3.3 第三段:递归与快速幂,负数参数是死循环的开关
第三段是递归形式的快速幂,customFunction(a, b)求 a 的 b 次方。第 27 题输入 2 和 3,问返回值是不是 64,答案为假,2³ = 8。第 30 题输入 5 和 4,返回 625,选 B,5⁴ = 625。
第 28 题问 b 为负数时会不会陷入无限递归,答案为真。原因藏在递归结构里:快速幂的递归分支要么走b - 1要么走b / 2,两个方向都是朝 0 收敛的,b 一旦为负,b / 2在整数除法下还是负数甚至原地打转(-1 / 2 = 0 只在 C++11 之后才确定,-3 / 2 = -1 会一直循环),永远到不了终止条件b == 0,栈帧越堆越深,最后栈溢出。第 29 题问 b 越大运行时间是否越长,答案为真,递归深度与 log b 同阶。
这里有个极易忽略的细节:第 27 题问的是「函数返回值」,第 31 题问的是「程序最终输出」,两个题干只差几个字,答案却差了一整套运算——第 31 题输入 3 和 3 时最终输出是 144,而不是 27,说明主函数里在函数返回值之外还做了一层组合运算。读这类题,第一步永远是分清「问函数」还是「问程序」。
非递归模板更适合考场默写:
long long qpow(long long a, long long b) { long long ans = 1; while (b > 0) { // b 必须为非负,负数直接返回 0 或特判 if (b & 1) ans *= a; // 当前二进制位为 1,累乘进答案 a *= a; // 底数平方 b >>= 1; // 指数右移一位 } return ans; }b & 1判断最低位,b >>= 1相当于除以 2,两行配合就是二进制拆分指数。第 32 题把递归改成return a + customFunction(a - 1, b - 1),输入 3 和 3 时最终输出 36,本质是变成等差数列求和再叠主函数运算,改成加法后增长从指数级掉到线性级,这也是「改一行看复杂度变化」的典型考法。
3.4 阅读程序的三步固定流程
三段程序读完,可以抽出一套通用流程,考场上按这个顺序走,能省一半时间。
第一步,先看主函数输入输出,确定「喂什么、吐什么」。很多判断题根本不用读函数体,看输入输出格式就能排除一半选项。第二步,找循环边界和递归终止条件,把所有<、<=、i*i、n/2圈出来,这些是出题人放钩子的地方,第 17、20、22 题全落在这个位置。第三步,遇到「改一行」型题目,先在草稿纸上重写改后的完整片段,再代入小数据试算,绝对不要在原逻辑上打补丁心算。
4. 完善程序:两道题,一套填空逻辑
完善程序两大题各 5 空、每空 3 分,答案如下:
| 题号 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
|---|---|---|---|---|---|---|---|---|---|---|
| 答案 | A | B | D | C | D | B | B | B | B | C |
4.1 完全平方数:bound 为什么是 floor(sqrt(num))
第一题判断正整数 n 是否为完全平方数,程序用枚举 i 的方式找是否存在 i 使 i² = n。五个空分别填:i 初值为 1、bound 为(int)floor(sqrt(num))、判断条件num == i * i、命中返回true、循环走完返回false。
关键在第二个空。为什么上界取 √num 而不是 num/2 或 num−1?因为若 n = i²,则 i ≤ √n,枚举到 √n 就覆盖了全部可能因子,取 num/2 虽然也对但慢一个量级,取 num−1 则退化到 O(n)。为什么外面要套(int)floor(...)?sqrt返回浮点数,直接赋给 int 会截断,而截断方向对完全平方数恰好安全(9 的 sqrt 是 3.0,截断还是 3),但如果不加 floor,遇到 8.999999 这类浮点误差就可能少枚举一个值,(int)本身就是向零截断,floor 在这里是双重保险。
第三个空填num == i * i而不是num = 2 * i,这是全卷最典型的送分陷阱:判断相等用双等号,单等号是赋值,写进 if 条件里不仅逻辑错,还会因为赋值结果非零而恒真。第四、五个空分别填true和false,位置不能颠倒——命中就返回真,循环结束还没找到就返回假。
bool isSquare(int num) { if (num < 0) return false; int i = 1; int bound = (int)floor(sqrt((double)num)); // 上界取根号,不能取 num/2 for (; i <= bound; ++i) { if (num == i * i) return true; // 判断相等,不是赋值 } return false; // 循环走完仍未命中 }更稳的写法是干脆不用 sqrt,把条件写成i * i <= num,既避开浮点误差又省一次函数调用,代价是循环次数多一点点,但对竞赛数据完全够用。还有一个边界值得留意:num = 0 时 bound = 0,i 从 1 开始,一次都不进循环,返回假——而 0 其实是完全平方数。题干限定正整数,所以不影响答案,但自己写函数时要补上这一条。
4.2 汉诺塔:三段递归与参数轮换
第二题是汉诺塔,五空答案依次是:终止条件i == 1、终止时的移动move(src, tgt)、第一次递归dfs(i-1, src, tgt, tmp)、第二次递归的盘子数i-1、第二次递归的参数dfs(i-1, tmp, src, tgt)。
理解这段代码只需要一个动作:把「搬 n 个盘子」拆成「搬上面 n−1 个 → 搬最底下 1 个 → 再搬上面 n−1 个」。第一次递归时,最底下的盘子还没动,上面 n−1 个要从 src 挪到 tmp,此时 tgt 只能当中转,所以参数顺序是(src, tgt, tmp);第二次递归时,最底下的盘子已经落在 tgt 上,剩下 n−1 个要从 tmp 挪到 tgt,src 空出来当中转,所以是(tmp, src, tgt)。
void dfs(int i, char src, char tmp, char tgt) { if (i == 1) { move(src, tgt); return; } // 只剩一个盘子,直接搬 dfs(i - 1, src, tgt, tmp); // 上面 i-1 个:src -> tmp,借 tgt 中转 move(src, tgt); // 最大的那个:src -> tgt dfs(i - 1, tmp, src, tgt); // 上面 i-1 个:tmp -> tgt,借 src 中转 }参数含义固定:i是当前要搬的盘子数,src是起点柱,tmp是中转柱,tgt是目标柱。记一条口诀就不会填错——第二个参数和第四个参数之间夹着的那个永远是「借用的柱子」,中转柱在递归调用时总是在中间位置挪来挪去。总移动次数是 2ⁿ − 1,n = 20 时已经超过一百万,这也是为什么递归题里一旦终止条件写错,程序不是答错而是直接跑不完。
4.3 填空五步法:从变量角色倒推空位
完善程序的空看着吓人,其实每个空都能用同一套流程定位。第一步,先通读一遍,标出所有变量,判断谁是计数器、谁是边界、谁是结果;第二步,找出空所在语句的类型,是初始化、是判断条件、还是返回值,类型一确定,选项范围立刻缩小;第三步,用一组小数据手算,比如完全平方数用 9、汉诺塔用 n = 2,看空位应该产生什么值;第四步,检查对称性,递归题的参数往往是成对出现的,第一个递归填了(src, tgt, tmp),第二个大概率是(tmp, src, tgt);第五步,回填后整体走一遍,确认没有把==写成=、没有把true和false放反。
5. 易错点:五个最容易丢分的地方
把整张卷子的错题归归类,会发现失分不是均匀分布的,而是集中在五个固定位置。每一条都按「易漏点 → 为什么漏 → 怎么补」拆开。
5.1 概念边界类:int 范围与存储单位
易漏点一:把 int 的范围当成正负对称。很多人记成 −2³¹ 到 2³¹,多算了一个正数。为什么漏?因为十进制写出来太长,平时写代码几乎不碰边界,脑子里只有「大概二十一亿」这个模糊印象。怎么补?把「n 位有符号范围是 −2^(n−1) ~ 2^(n−1)−1」当成公式背,再配一个具体数字锚点:int 最大值 2147483647,最小值比它绝对值大 1。顺手把 unsigned int 的 0 ~ 4294967295 一起记,两者一对比,不对称这件事就再也不会忘。
易漏点二:单位换换算只换一半。1MB 算成 1048576 就交卷,忘了乘 8。为什么漏?因为题目问的是「二进制位」,而脑子里默认单位是字节,读到 MB 就自动跳到字节数。怎么补?养成「先把单位链写全再算」的习惯:MB → KB → B → bit,每跨一级写一个乘号,1024 × 1024 × 8 写出来再按计算器,比心算安全得多。
5.2 程序语义类:循环边界与返回值/输出的区别
易漏点三:看到「改一行」就以为结果一定变。第 17 题把i * i <= n改成i <= n/2,循环次数确实变了,但判定结果没变,答案依然是 8。为什么漏?因为注意力全被「改了」吸走,默认改动必然影响输出。怎么补?遇到这类题强迫自己走一遍「改动影响的是次数还是结果」这个判断,具体做法是代入最小和最大的两个输入各算一次:n = 2 和 n = 20,两次结果一致才能判定「不变」。
易漏点四:把「函数返回值」和「程序输出」混为一谈。第 27 题问customFunction(2,3)的返回值,第 31 题问程序最终输出,前者是 8、后者是 144,题干只差几个字。为什么漏?因为阅读程序题读到最后已经疲劳,眼睛扫过去只抓到函数名和数字。怎么补?读题干时用笔把「返回值」「输出」「返回」这几个词圈出来,圈完再动笔,这个动作只要两秒,能救回一整道选择题。
易漏点五:以为下标写错会编译报错。第 22 题把dp[i-1]改成dp[i-3],问会不会编译错误,答案是不会。为什么漏?因为日常调试时越界经常表现为崩溃,容易把「运行时错误」和「编译错误」混成一类。怎么补?把三类错误分开记:语法错误和类型错误在编译期拦,数组越界、除零、死递归都在运行期才炸,编译期一个都不拦。这条记牢,阅读程序里所有「会不会编译错误」的判断题都能秒答。
5.3 算法原理类:二分次数、栈序与遍历还原
易漏点六:二分查找次数忘记向上取整。1000 个元素最多比较 10 次,写成 9 或 25 都是没走完对数这一步。为什么漏?因为 log₂1000 ≈ 9.97,四舍五入的直觉会把它拉向 10,但如果是 1024 个元素就正好 10,两题连着做容易串。怎么补?记「2 的幂次表」:2⁹ = 512,2¹⁰ = 1024,元素个数落在哪两个幂之间,答案就是上面那个指数。
易漏点七:栈的出栈序列靠感觉判。第 13 题四个选项,前三个都能凑出来,只有 D 不行,但如果不用规则硬凑,很容易把能凑的判成不能。为什么漏?因为模拟过程要在脑子里维护一个栈,超过 4 个元素就记不住栈内状态。怎么补?用「后面比它小的元素必须降序」这条口算规则,拿 D 里的 5 一试:后面比 5 小的有 2 和 4,出现顺序是 2 在前 4 在后,升序,违规,直接判死。规则比模拟快,也比模拟准。
6. 复习清单:把这张卷子拆成可以逐项打勾的动作
复习到这一步,最有用的不是再刷一套新题,而是把这张卷子的失分点转成一张可执行清单。下面这张表按分数块拆开,左列是考点,中间是这张卷子里的具体落点,右列是可以直接执行的动作。
| 分数块 | 具体落点 | 复习动作 |
|---|---|---|
| 单选 30 分 | int 范围、MB→bit、struct 与循环、编译器职责 | 抄一遍「n 位有符号范围」和「单位换算链」,各配一个反例 |
| 单选 30 分 | 混合进制、格雷码 | 手写 g = b ^ (b>>1),把 0 到 7 的对照表默写一遍 |
| 单选 30 分 | 分类加法、捆绑法、握手定理、二分次数 | 每类只记一句触发条件:相邻就捆绑、至少一个就分类、度数和看边数两倍 |
| 单选 30 分 | 前中序还原后序、栈序判定 | 用第 12、13 题各重做一遍,栈序必须用「后面小数降序」规则验 |
| 阅读程序 40 分 | isPrime 边界、i*i<=n、sumPrimes 语义 | 默写带(long long)强转的判素模板,标出三处边界 |
| 阅读程序 40 分 | 一维 DP 的下标错位、答案位置、越界不报编译错 | 手推第 24、25 题两张 DP 表,至少推 6 行 |
| 阅读程序 40 分 | 递归快速幂、b 为负死递归、返回值与输出 | 默写非递归 qpow,写清 b ≤ 0 的特判 |
| 完善程序 30 分 | 完全平方数的 bound、==与=、true/false 位置 | 用 1、4、9、10 四个数各跑一遍自己写的 isSquare |
| 完善程序 30 分 | 汉诺塔三段递归、参数轮换、2ⁿ−1 | 用 n = 2 和 n = 3 手推移动序列,对照代码逐行核 |
表里的动作用不了一整个下午,但每一条都要落到「手写一遍」而不是「看一遍」。尤其是 DP 表,看别人推觉得理所当然,自己推三行就会开始犹豫下标对不对,这个犹豫就是考场上要丢的分。
如果时间只剩一天,我会按这个顺序过:先把单选的 15 个答案遮住重做一遍,错的题在表里找到对应行;再把阅读程序三段代码抄到纸上,不看解析自己标出循环边界和终止条件;最后把完善程序两个模板各默写一次,写完用 n = 9 和 n = 3 验证。三步走完,这套卷子的边际收益基本榨干了。
再多说一句查漏补缺的判断标准:如果某道题你能说出答案但说不出「为什么其他三个选项错」,那这道题算没掌握,只能算记住。真正掌握的标准是能指出干扰项错在哪个具体位置——第 1 题的 D 错在对称假设,第 17 题错在把「循环次数变」当成「结果变」,第 22 题错在把运行期错误当成编译期错误。能指到这一层,下次换个数字也照样能做对。
我自己的血泪经验是:第一遍做这张卷子时,阅读程序第 20 题我选了 C,理由是「输出 3 和 10 也说得通」,完全没意识到i <= n会让每个数都被判成合数。后来复盘才发现,我不是不会判素,是没把「循环上界改到 n」和「n % n == 0」这两件事连起来。从那以后,我每次读带循环边界的程序,
本文还有配套的精品资源,点击获取