ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CSP-J初赛选择题训练2021:分块刷题与错题复盘

CSP-J初赛选择题训练2021:分块刷题与错题复盘

很多人第一次认真做CSPJ初赛选择题训练2021的时候,都会经历一个很别扭的阶段:十套卷子刷完,正确率纹丝不动,错的地方还是那几处——补码的边界、二叉树的叶子数、快排的最坏情况、代码段里循环变量到底跑了几轮。对完答案,看一眼解析,心里默念"哦,是这样",然后下一套继续错。问题不在刷题量,在于刷题的方式:把选择题当成"判断题"来做,只关心对错,不关心每个选项为什么错、错在哪个前提上。这篇东西想聊的就是怎么把一套选择题拆开用——2021年那份卷子恰好是个不错的样本,它的题型分布比较均衡,难度也没有刻意堆难题,用来做系统性训练非常合适。适合刚接触入门级认证、打算把第一轮稳稳拿下的同学,也适合带学生、需要一套可复用的训练流程的教练参考。

1. 2021年这套选择题的命题画像:先把靶子看清楚

1.1 十五道单选题的五大板块与权重

第一轮卷面里,单项选择通常是十五题、每题两分,占了整卷的三成。剩下的分数在阅读程序和完善程序上,但那两部分本质上还是在考同一批知识点,只是换了个"让你读代码"的包装。所以选择题这一块如果垮了,后面两部分基本也撑不住。

把2021年那份卷子的单选题按知识点归类,大致能分出五个板块:

板块典型题量常见考法
计算机基础与编码3~4题存储单位换算、字符编码、进制与补码、硬件与系统常识
数据结构3~4题二叉树性质与计数、图的边数与遍历、栈与队列、哈希
算法与复杂度3题左右排序算法特性、时间/空间复杂度判断、算法思想辨识
数学与组合计数3~4题排列组合、容斥、同余、递推、概率期望
网络与其他常识1~2题协议分层、地址与端口、数据库基本概念

这张表不是让你背题量,而是让你在做训练计划时知道权重。很多人的复习顺序是"先刷自己会的",结果数学与组合这块一直拖到最后,而那恰恰是题量不小、提分空间又最大的一块。我见过有同学把进制转换练到滚瓜烂熟,组合计数题一错就是两三分,性价比完全反了。

1.2 为什么"看着最眼熟"的题反而最容易丢分

这里有个反直觉的现象:卷面上失分最多的,往往不是那些一眼看不懂的题,而是那些"感觉做过一百遍"的题。原因很简单,熟悉感会让人跳过审题这一步。

举几个2021年那类卷子里反复出现的典型场景。题目问"n个结点的二叉树的最小高度",你脑子里立刻蹦出 log₂n,但如果题干写的是"完全二叉树"或者"每个结点最多两个子结点",答案的取整方式就不一样;题目问"下列排序算法中 unstable 的是",你知道快排不稳定,可选项里同时放了希尔排序和堆排序,三个都不稳定,这时候考的是你知不知道哪个"最稳定地不稳定"。这类题的共同点是:知识点你都会,丢分丢在条件的读法上。

所以我在训练里加了一条硬规矩:每道选择题读两遍,第一遍读题干,第二遍专门读限定词。限定词包括"最多/最少""一定/可能""平均/最坏""完全/满""有向/无向""循环/非循环"。把这些词用笔圈出来,正确率的提升比多刷五套卷子明显得多。

1.3 从卷面反推的知识边界:哪些可以不深挖

训练选择题最容易犯的第二个错误是"过度扩张"。看到一个不会的名词就去翻资料,一晚上过去,卷子只做了三道题,学到的东西大概率明年也不考。

从2021年那份卷子的实际命题来看,边界其实画得挺清楚。计算机基础这块,考的是"够用"级别的常识——存储单位的进位关系、常见字符编码的字节数、CPU与内存的基本分工,不会去考指令流水线的细节;数据结构这块,重点在性质与计数的推导,不会让你手写红黑树的旋转;算法这块,认得出算法思想、说得出复杂度就够了,不会给一段代码让你精确数出常数因子。

我一般会给一个可操作的标准:某道题背后的知识点,如果推导过程超过三行,就只记结论和适用条件;如果三行以内能推出来,就必须会推。二叉树叶子数公式三行能推,背;平衡树的几种旋转,超过三行,记名字和适用场景就够。按这个标准筛一遍,复习范围能砍掉三分之一,省下来的时间全部投到必须会推的那部分上。

2. 进制、补码与编码:丢分不在计算,在规则边界

2.1 补码运算里那条"越界就翻转"的隐性规则

补码题是选择题里的常客,也是"会算但会错"的重灾区。基本规则大家都记得:正数的补码是本身,负数的补码是原码除符号位外取反再加一。但真正让题目变难的是边界。

八位有符号整数能表示的范围是 -128 到 127,这个数字很多人背得出来,但背完之后不会用。考题经常这样设计:给一个运算,问结果是多少。比如 -128 取相反数。按数学直觉应该是 128,但八位表示不了 128,结果还是 -128。这类"溢出不报错、直接绕回来"的行为,正是补码设计的特点,也是命题人最喜欢下刀的地方。

我在训练时会要求把补码题的判断流程固定成三步,一步都不能跳:

  1. 先定字长。题目说"八位""十六位""用 n 位表示",第一件事是把位宽抄在草稿纸角落。没有位宽就没有溢出,也就没有答案。
  2. 写出范围。八位就是 [-128, 127],十六位就是 [-32768, 32767]。范围写出来,溢出与否一目了然。
  3. 算完再回到范围内检验。算出的结果如果落在范围外,说明发生了回绕,需要按模 2ⁿ 折算回来。

这三步看起来笨,但能拦住绝大部分"算对了数、选错了项"的情况。我统计过自己做错的补码题,超过一半不是不会算,而是算完没做第三步检验,把一个数学上正确、但在该位宽下不存在的值选了上去。

提示:补码题里还有一个高频陷阱是"无符号"与"有符号"的混用。题干如果只写"n位二进制数"而没写有无符号,一定要看选项——选项里如果出现了负数,说明按有符号理解;如果全是非负数且最大值是 2ⁿ-1,说明按无符号理解。这个判断必须先做,否则后面全错。

2.2 编码与存储单位的混淆点

编码类题目表面简单,实际有一堆容易混的细节。字符编码这块,常见的是问"某个字符集下,n个字符占多少字节"。这里的关键是分清"字符数"和"字节数"是两回事——一个英文字母在一个字节的编码里占一字节,一个汉字在双字节编码里占两字节,但如果换成变长编码,同样是汉字可能占三字节。题目如果只给了字符数量没给编码方式,那这道题本身就有问题,正常卷面不会这么出,所以看到这类题要先确认题干有没有交代编码。

存储单位这块的坑在进位。很多人下意识觉得 1KB = 1000B,因为日常生活里"千"就是一千。但存储单位用的是 1024 进制,1KB = 1024B,1MB = 1024KB。更进一步,如果题目里出现"带宽"或者"传输速率",那又是另一套进位(通常按 1000)。这两种进位混在一道题里,是最常见的失分点。

我给自己的记法是:凡是"存"的,按 1024;凡是"传"的,按 1000。存量的描述用容量单位,传输的描述用速率单位,看到单位里的"每秒"就往 1000 想。这个方法不严谨,但应付选择题足够,而且比死记硬背靠谱。

2.3 一套可以闭眼执行的手算流程

进制转换本身没什么难度,难在考场上时间紧、心慌,容易在中间某一步算错一位,后面全崩。我给这套流程定了个规矩:任何进制题都在草稿纸上写出完整的中间过程,不许心算。

十进制转 r 进制用除 r 取余,从下往上读;r 进制转十进制用按权展开,从高位往低位加。这两条谁都知道,但真正执行的时候,很多人会在"余数倒序"这一步翻车——算完从下往上抄的时候漏掉一位,或者把最高位的余数写到了最低位。

一个更稳的做法是先估位数。要转的数如果是 n,目标进制是 r,那么结果的位数大约是 log_r n 向上取整。比如把 100 转成二进制,2⁶ = 64,2⁷ = 128,所以结果一定是七位。有了位数预期,抄余数的时候就能对得上,少一位多一位立刻能发现。

再补一个提速技巧:二进制和十六进制、八进制之间的转换不用绕道十进制。四位二进制对应一位十六进制,三位二进制对应一位八进制,从左往右分组,不足补零,直接换。选择题里如果出现"二进制转十六进制",用分组法比先转十进制再转十六进制快一倍,而且不容易错。

3. 树与图:所有计数题都能从三个母公式推出来

3.1 二叉树度数关系式的通用推法

二叉树这块题目花样很多,但底层其实就靠着几个关系式在转。最核心的一条是:叶子结点数 = 度为2的结点数 + 1。这条公式我从来不死记,因为推起来只要两行。

设度为 0、1、2 的结点数分别是 n₀、n₁、n₂。结点总数 n = n₀ + n₁ + n₂。边数方面,除根结点外每个结点都有一条入边,所以边数 = n - 1;同时边数也等于所有结点的出度之和,即 0·n₀ + 1·n₁ + 2·n₂。两个式子联立:n₀ + n₁ + n₂ - 1 = n₁ + 2n₂,化简得 n₀ = n₂ + 1。

两行推完,比背公式可靠得多,而且这个推导过程本身在"阅读程序"部分也会用到——有些程序题就是让你算树的结点数,认得这个关系式,题目就从"读代码"变成了"套公式"。

完全二叉树还有几条常用结论:n 个结点的完全二叉树高度是 ⌊log₂n⌋ + 1;按层序从 1 开始编号时,编号为 i 的结点,左孩子是 2i,右孩子是 2i+1,父结点是 ⌊i/2⌋。这三条必须滚瓜烂熟,因为它们在"堆"相关的题目里会反复出现,而堆本质就是完全二叉树。

3.2 哈夫曼树与WPL:别急着画整棵树

哈夫曼树的题目通常有两种问法:给一组权值,问构造出的哈夫曼树的带权路径长度(WPL)是多少;或者问某个字符的编码长度。

很多人的第一反应是把整棵树画出来,然后一层一层算。这个方法没错,但慢,而且画到四五个叶子之后容易乱。更快的做法是用"合并求和"的方式直接算 WPL:每次取最小的两个权值合并,把合并结果累加到总和里,然后把合并值放回集合继续,直到只剩一个数。累加出来的总和就是 WPL。

举个例子,权值是 2、3、5、7、11。第一次取 2 和 3,合并成 5,总和记 5;集合变成 5、5、7、11。第二次取 5 和 5,合并成 10,总和累加到 15;集合变成 7、10、11。第三次取 7 和 10,合并成 17,总和累加到 32;集合变成 11、17。最后一次合并成 28,总和 60。所以 WPL 是 60。

这个方法的好处是不用画图,全程只做加法和取最小值,草稿纸上就是几行数字,出错概率低很多,速度也快。考场上时间紧的时候,这个差别很实在。

3.3 图论题里那些"条件没读完"的坑

图论的选择题最典型的问题是条件读不全。同一个"n个结点的图最多有多少条边",有向图是 n(n-1),无向图是 n(n-1)/2,完全图和不一定是完全图又是两回事。题目里的每一个限定词都在改变答案。

我整理过一份"图论题限定词清单",做题时逐条对照:

限定词影响
有向 / 无向边数公式差一倍
简单图 / 允许重边允许重边时"最多边数"没有意义,要看题目是否另有限制
连通 / 强连通影响最少边数,n 个结点连通至少 n-1 条边
完全图边数取到最大
树 / 生成树边数固定为 n-1

还有一类题考遍历序列。给一棵二叉树的前序和中序,问后序是什么。这类题的做法是固定的:前序的第一个是根,在中序里找到根的位置,左边是左子树、右边是右子树,然后递归。手算的时候我建议直接在草稿纸上画树,画完再写后序,比在脑子里递归可靠。画树看着慢,但正确率高,而且不容易在中途忘记自己算到哪了。

4. 排序、复杂度与算法思想:选择题里最会骗人的一块

4.1 稳定性表格要背,但要背对

排序算法的稳定性是必考项,标准答案大致是:冒泡、插入、归并、基数稳定;选择、快速、堆、希尔不稳定。这张表必须背,但光背表不够,因为题目经常问"下面哪个排序算法在某种情况下是稳定的",这时候考的是你知不知道稳定性是怎么来的。

稳定性的本质是:排序过程中,相等元素之间的相对次序是否可能被交换。冒泡排序只在相邻元素严格逆序时交换,相等不换,所以稳定;选择排序要把最小值换到前面,这个交换可能跨过相等元素,所以不稳定;快速排序的分区过程会把元素甩到很远的位置,也不稳定。理解了这一层,"某种情况下是否稳定"这种题就不用猜了。

希尔排序是个特例,它的稳定性依赖于增量序列,一般按不稳定处理。题目如果专门考希尔排序的稳定性,通常是想考"它不稳定"这个结论,不会去纠缠具体的增量选择。

4.2 平均与最坏的错位

复杂度题最常见的套路是把平均和最坏混在一起。快速排序平均 O(n log n)、最坏 O(n²),归并排序两者都是 O(n log n),堆排序两者也都是 O(n log n)。题目如果说"下列排序算法中,最坏情况下时间复杂度最优的是",答案是归并或堆排;如果说"平均情况下",快排、归并、堆排都是 O(n log n),这时候要靠空间复杂度或者其他性质来区分。

这类题我一般会先在草稿纸边上写一个小的对照表,把几个常考算法的时间(平均/最坏)、空间、稳定性列出来,做完整套卷子再擦掉。写一遍比看十遍记得牢,而且写的过程本身就是一次梳理。

空间复杂度这块也有坑。归并排序需要 O(n) 的辅助空间,快排平均 O(log n)(递归栈),堆排 O(1)。题目问"原地排序"时,堆排和快排(不算递归栈的话)都算,归并不算。这个区别在选择题里经常出现。

4.3 贪心、分治、动态规划的辨识题

算法思想的辨识题通常给一段描述或者一个具体问题,问"这最适合用什么方法解"。这类题不考实现,考的是判断标准。

我的判断顺序是这样的:先看问题有没有"最优子结构 + 重叠子问题",两个都有就是动态规划;只有最优子结构、没有重叠子问题,可能是分治也可能是贪心;再看能不能"每一步都取当前最优且不影响后续",能就是贪心。举个典型对比:最短路径里,单源无负权用贪心(每次取当前最近的未访问点),任意两点之间用动态规划(逐步放宽中间点);而"n个数里找最大子段和",看着像贪心,实际要用动态规划,因为局部最优不保证全局最优。

这类题错一次不可怕,可怕的是错完之后没搞清楚为什么。我要求自己每道辨识题都写一句"为什么不是另一种方法",写不出来就说明没真懂。

5. 程序阅读类选择题:把"读代码"变成"跟踪状态"

5.1 手推表的四个必要列

阅读程序那部分虽然不是单选题,但里面的判断题和小选择题,思路和选择题是一回事。核心方法只有一个:手推。不许心算,不许"感觉应该是这样"。

手推表我固定用四列:循环变量、关键中间变量、条件判断结果、输出。以最简单的循环累加为例:

轮次isum条件 i<=n输出
初值10--
111真-
223真-
336真-

四列看起来啰嗦,但它的价值在于出错时能立刻定位。如果最终答案不对,你可以沿着表格往回找,看是哪一轮的哪个变量算错了;如果是纯心算,错了只能重来。

5.2 位运算常见套路库

程序阅读题里位运算出现频率很高,因为代码短、效果明显。常见的几个套路必须认得:

  • n & (n-1):把 n 的最低位的 1 变成 0。连续做直到 n 为 0,次数就是 n 的二进制中 1 的个数。
  • n & (-n):取出 n 的最低位的 1 及其后面的 0,得到 lowbit。
  • n ^ (n >> 1):格雷码转换。
  • x << k等价于乘 2ᵏ,x >> k等价于除以 2ᵏ 向下取整。

认得这些套路,很多"看起来复杂"的代码段一眼就能看出在干什么。比如一段循环里反复执行n &= n-1并计数,那不管变量名起得多花哨,它就是在数 1 的个数。

5.3 递归题的展开深度控制

递归是最容易让人在草稿纸上写乱的部分。我的做法是只展开到能看出规律为止,不展开到底。

比如一个递归函数,参数每次减一,边界是 0 或 1,那展开三四层就能看出它是线性的,直接写通项;如果参数每次减半,展开三层看是否是二分;如果参数从 n 变成 n-1 和 n-2(斐波那契式),那要警惕指数级复杂度,题目很可能是问"这段代码的时间复杂度"。

判断递归复杂度有个粗办法:数一数递归树每一层的结点数和层数。每层结点数不变、层数是 log n 的,是 O(n log n);每层结点数翻倍、层数是 n 的,是指数级。这个方法不精确,但选择题够用。

6. 三轮刷题节奏:从分块专练到限时整卷

6.1 第一轮按知识块切,不按年份切

第一轮的目标是"补洞",不是"提分"。所以这一轮不要按年份成套做,而是按知识块做。具体做法是把2021年那份卷子的选择题按第一章的五个板块拆开,同一板块的题集中做,做完立刻复盘。

这样做的原因是同类题集中出现时,错误模式会暴露得更明显。如果一道进制题错了,你只会觉得"这题没看清";但如果连着十道进制题错了四道,而且错法一样,那就是方法有问题,需要改流程,不是改态度。

第一轮的题量不一定要大。我一般建议每个板块做两到三套卷子对应的题就够了,重点是每道错题都要写清错因。

6.2 第二轮限时整卷与错因打标

第二轮开始做整卷,但有一个硬要求:限时,而且记录每题耗时。

选择题部分的合理节奏是十五题用二十分钟左右,平均一题八十秒。有些题会拖时间,比如程序阅读类的复杂代码,这种允许超到三分钟,但要有意识地补偿——后面的题必须加速。

记录耗时的方法是做完整卷后,在每道题号旁边画正字。做完之后你会看到一批"耗时超过三分钟但做对了"的题,这些是隐患,考场上如果状态不好,这些题就会变成错题。

同时给每道错题打错因标签,我一般用这几类:知识盲区、审题失误、计算错误、规则混淆、时间不够。标签的作用是让复盘有针对性,知识盲区去补知识,审题失误去改读题习惯,这两类问题的解法完全不同。

6.3 第三轮只刷错题和同源题

第三轮不再做新卷子,只做错题,而且要做"同源题"——也就是同一知识点、不同问法的题。

具体做法是:错题本上每道题的旁边写一个知识点标签,比如"补码溢出"、"完全二叉树高度"、"快排最坏"。然后找同一个标签下的其他题再做一遍。这样做的好处是检验你是否真的掌握了这个知识点,而不是记住了某道题的答案。

第三轮的时间应该最短,一般占整个复习时间的两成左右。如果第三轮发现自己还在大面积错同一种类型的题,说明第一轮的方法没执行到位,需要回去重来。

6.4 时间分配与答题顺序

考场上的时间分配我建议按"稳拿分优先"的原则。选择题里通常会有一两道一眼能看出答案的送分题,也有一两道需要想一想的。我的顺序是:先扫一遍,把一眼就会的做完,再回头处理需要想的,最后处理完全没思路的。

不建议在一道题上死磕。选择题每题才两分,花五分钟做对一道,不如花五分钟检查五道。如果一道题超过九十秒还没有明确方向,先标记、跳过,等全部做完再回来。

还有一点:选择题做完之后不要立刻进入下一部分,留两三分钟快速检查一遍答题卡填涂。涂错位这种情况每年都有,检查成本极低,收益极高。

7. 错题本的具体写法与复盘节奏

7.1 一条错题记录该有哪几个字段

错题本不是抄题本。抄题干、抄解析、抄答案,抄完一遍什么也没记住。我要求每条记录只写五个字段:

  1. 题干关键条件(不超过两行,只写改变答案的那些条件)
  2. 我的错误答案 + 当时的想法(写清为什么选了这个)
  3. 正确答案 + 一句话理由
  4. 错因标签
  5. 同源题索引(这个知识点还出现在哪几套卷子里)

第三项和第五项是核心。写清"为什么错"比抄十遍正确解答有用,而"同源题索引"能让你在复习时快速找到同类题。

7.2 复习周期的安排

错题复习用间隔重复的思路:做完当天看一遍,三天后看一遍,一周后看一遍,考前再看一遍。

关键在第二遍和第三遍。第一遍刚做完,题目还热乎,看一眼就懂;三天后看的时候,如果还能立刻说出错因和正确思路,说明真掌握了;如果卡住了,说明只是短期记忆,需要重新过一遍知识点。

我自己的经验是,第二遍卡住的题,往往是那种"当时看解析觉得很简单"的题。这类题最危险,因为简单感是解析给的,不是你自己想出来的。

7.3 一个模拟案例的完整复盘

假设有同学做错了一道题,题干大意是"一棵有 n 个结点的完全二叉树,叶子结点的个数是多少",他选了 n/2,正确答案是向上取整的 (n+1)/2。

按五个字段记录会是这样:

关键条件是"完全二叉树"和"叶子结点数"。他的想法是"完全二叉树叶子大概占一半",直接写了 n/2,没有验证。正确答案是 ⌈(n+1)/2⌉,理由是可以用 n₀ = n₂ + 1 加上完全二叉树的性质推出——完全二叉树里度为 1 的结点最多只有一个。错因标签是"公式推导不熟"。同源题索引指向另外两套卷子里考"完全二叉树高度"和"结点编号关系"的题。

这样一条记录写完,下次再遇到完全二叉树的题,他会先想"能不能用 n₀ = n₂ + 1 推",而不是凭感觉估一半。这才是复盘真正起作用的地方。

8. 考场上那四十分钟的取舍策略

8.1 卡住超过九十秒的处理方式

选择题最容易出问题的时刻,是遇到一道"明明记得,但就是想不起来"的题。这种状态下人会不自觉地反复读题,时间一点点流走。

我的处理方式是九十秒原则:一道题超过九十秒还没形成明确思路,立刻标记跳过。跳过之后不要在脑子里继续想它,因为分心会让后面的题也做不好。等全部做完,回头再看,往往五分钟想不出来的题,换个心情两分钟就通了。

还有一种情况是"排除到只剩两个选项,但选不出来"。这种时候如果实在选不出,就选那个"看起来更规范"的——出题人设计干扰项时,通常会让错误选项带一点"过于绝对"或"过于特殊"的味道。这是玄学,但在没有更好办法的时候,比随机猜略好。

8.2 检查阶段到底该查什么

检查不是把题重做一遍,那等于做两遍题,时间不够。检查要有重点:

第一查填涂。答题卡上的选项和试卷上写的是否一致,尤其是中间跳过又回来做的那些题,最容易错位。

第二查标记过的题。那些九十秒原则跳过的题,这时候重新看,往往有新思路。

第三查限定词。"最多/最少""一定/可能",这些词在匆忙中很容易看漏。我一般会把标记过的题的限定词再读一遍。

第四查计算类题的最后一步。进制转换、树的高度、WPL 这类题,最后一步是最容易出错的,重新核算一次末步的成本很低。

9. 几个我一直没改掉的坏习惯(以及改法)

第一个坏习惯是看解析只看对的。做错的题,看解析确认正确答案之后就翻页,不去看错误选项为什么错。后来发现,很多干扰项的设计是有套路的,比如把一个常见公式里的"加一"去掉,或者把"最坏"换成"平均"。看懂干扰项,比看懂正确答案更有价值,因为正确答案只有一个,干扰项会反复出现。

第二个坏习惯是用"刷题量"代替"复盘量"。一天做三套卷子,感觉很充实,但每套只花十分钟复盘,错的东西还是错的。后来我把节奏改成一天一套,复盘时间比做题时间还长,正确率反而上去了。做题是发现问题,复盘才是解决问题,顺序不能倒。

第三个坏习惯是回避不擅长的板块。数学与组合计数一直是我的弱项,做题时会下意识先做别的,把组合题留到最后,最后往往没时间做。改法是强制自己每天先做五道组合题,做不完不做其他。坚持两周之后,那种"看到组合题就烦"的情绪会淡很多,正确率也跟着上来。

最后分享一个我觉得最有用的技巧:把每道错题的正确思路压缩成一句话,写在便利贴上,贴在每天能看到的地方。比如"完全二叉树叶子数用 n₀=n₂+1 推"、"快排最坏 O(n²) 因为每次只分出一个"、"补码算完要回到范围内检验"。一周换一批,一个月下来能积累二三十句,这些句子比任何一本教材都贴身,因为每一句都是你自己踩过的坑。

返回列表