简介:CSP-J2024题目与答案解析整理为一份PDF文档,面向准备CSP-J初赛的学生及需要真题复盘的信息学教师,重点解决单项选择题与阅读程序题的考点梳理和答案核对问题。压缩包共1个PDF文件,大小约797KB,内容按单项选择题、阅读程序两大板块组织,便于在电脑或平板上直接查阅。已有7556人学习下载,说明其在新一轮CSP-J备赛中有较高参考度。文档不仅给出答案,还围绕int存储范围、格雷码、存储单位换算、C++基本数据类型与循环语句、ASCII码、二分查找、无向图度数和、二叉树遍历、栈操作、排列组合、编译器作用等考点展开题解;阅读程序部分则结合质数判断与统计、函数封装调用、数组和字符串输入输出、数值计算与位运算,帮助读者理解代码逻辑并定位易错点。
1. 抢下前 30 分:CSP-J 2024 的分数地图
CSP-J 2024 的卷面是 100 分:15 道单项选择 30 分、3 篇阅读程序 40 分、2 道完善程序 30 分。很多人备考时把时间全砸在写代码上,翻到第一页却懵了——int 存储范围、格雷码、存储单位换算、二叉树遍历、栈的出栈顺序,全是概念题,一行代码不用写,但每题 2 分,错三道就等于白丢一道完善程序大题。
这份 CSP-J2024 题目+答案解析真正的价值不在 ABCD 四个字母,而在「为什么是 C」这一段推理。下面按卷面顺序拆:选择题把 12 类高频考点连同真题一起过一遍,阅读程序讲逐行读代码的方法,完善程序讲怎么靠结构识别填对空,最后收在易错点和时间分配上。适合已经能写基础语法、但总在判断题和概念题上掉分的人。
2. 单项选择题:30 分里 12 个必背考点的抢分顺序
选择题的 15 道题有个很稳定的分布:计算机基础与存储换算 3~4 题、C++ 语言特性 3~4 题、数据结构与算法 4~5 题、组合计数 2 题、编译与系统常识 1~2 题。这四块里,前三块是可以靠背诵和固定套路拿满的,第四块才是真正需要动笔算的。答题顺序建议先扫一遍,把「不属于」「不是」「错误的是」这类否定题干圈出来,再按块做。
2.1 计算机基础与存储换算:int 范围、MB 换算、格雷码
第 1 题问 32 位 int 的存储范围。答案落在 -2147483648 ~ 2147483647,也就是 -2³¹ ~ 2³¹-1。这里有两个必须记死的点:一是有符号整数用补码表示,正数最大到 2³¹-1;二是负数比正数多一个,最小值是 -2³¹ 而不是 -(2³¹-1)。答题时如果四个选项里出现 -2³¹-1 或者 2³¹ 这种边界,基本都是干扰项。
第 5 题是存储换算:1KB 是 1024 字节,1MB 是 1024KB,1 字节占 8 个二进制位,所以 1MB = 1024 × 1024 × 8 = 8388608 bit,也就是 2²³。这类题的错误几乎全出在「字节到位的乘 8」这一步被漏掉,或者把 1024 当成 1000。记住口诀:K、M、G 之间乘 1024,字节到位乘 8。
第 4 题考格雷码。格雷码的定义是相邻两个编码只有一位不同,而且首尾也算相邻。直接背公式最快:第 i 个格雷码等于 i 异或 (i >> 1)。
# 生成 n 位格雷码,并检查相邻两项是否只差一位
n = 3
codes = [i ^ (i >> 1) for i in range(1 << n)]
for c in codes:
print(format(c, f'0{n}b'))
# 验证方法:相邻两项异或的结果应当是 1、2、4 这类 2 的幂
# 如果出现 3、5、6,说明这个序列不是格雷码
这段代码里 i ^ (i >> 1) 就是格雷码的生成公式, 1 << n 表示 2 的 n 次方。实战中不一定要真的跑代码,把 n=3 的序列手写出来是 000、001、011、010、110、111、101、100,对着选项逐个比对相邻位差异即可,比现场推公式快得多。注意最后一项 100 和第一项 000 也只差一位,这是很多人忽略的首尾条件。
2.2 C++ 语言特性:基本类型、循环语句、ASCII、编译器
第 6 题问哪个不是 C++ 基本数据类型,答案是 struct。C++ 的基本内置类型就是整型、浮点型、字符型、布尔型和 void 这几类,struct 是用户自定义的复合类型,虽然它也能像 int 一样声明变量,但归类上不属于基本类型。这类题有个小陷阱:选项里常混入 double 、 bool 、 void ,它们都是基本类型,别被「没见过」的感觉带偏。
第 7 题问哪个不是 C++ 循环语句,答案是 repeat-until。C++ 只有 for、while、do-while 三种循环,repeat-until 是另一种语言的直到型循环,语义上是「先执行再判断,条件成立才退出」。记住 C++ 的 do-while 是「条件为真继续」,和 repeat-until 的「条件为真退出」正好相反,出题人就爱在这个反向语义上做文章。
第 8 题是 ASCII 运算:(char)('a' + 13)。'a' 的 ASCII 码是 97,加 13 得 110,对应字符 n。这类题的通用做法是把小写字母 a~z 记成 97~122,大写 A~Z 记成 65~90,数字字符 0~9 记成 48~57。如果记不住具体数值,就用偏移量算:'a'+13 就是从 a 往后数 13 个字母,a 后面第 13 个是 n,数手指头都比查表快。
第 15 题考编译器的作用,答案是把源代码转换为机器代码。四个选项里「直接执行源代码」是解释器的行为,「进行代码调试」是调试器的功能,「管理运行时内存」是运行时环境或操作系统的职责。区分编译器和解释器是这类题的固定考点:编译器先整体翻译再执行,解释器边翻译边执行。
2.3 数据结构与算法:二分次数、度数和、二叉树、栈
第 9 题问 n 个元素的有序表用二分查找最多比较多少次。结论是 floor(log₂n) + 1。推导方式:每次比较后区间减半,最多减半到只剩一个元素,所以是「取 log₂n 向下取整再加 1」。做题时可以用小数值验算,n=1 是 1 次,n=2 是 2 次,n=8 是 4 次,把选项代进去试最快。
第 11 题问无向图所有顶点度数之和。答案是一条边贡献两个度,所以总度数等于边数的两倍。延伸记一下:有向图里所有顶点的入度之和等于出度之和,都等于边数。这两条经常连着考,写在一起背。
第 12 题是最经典的二叉树还原:已知前序 [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、E 分别是 B 的左右孩子;右子树同理,C 是根,F、G 是左右孩子。还原出来是一棵三层的满二叉树,后序遍历就是 D、E、B、F、G、C、A。答题时直接在纸上画树,比在脑子里推靠谱得多。
第 13 题考栈的出栈顺序。判断方法是模拟:按入栈序列依次进栈,每进一个就检查栈顶是不是当前该出栈的元素,是就弹出。更快的判断技巧是——如果某个元素先出了栈,那么比它先入栈、又比它后出栈的元素必须按逆序排列。比如入栈序列是 1 到 6,某个选项里 3 已经出栈了,后面的 1、2 却按 1 在 2 前面的顺序出,那这个选项一定不可能。这类题不需要算,逐个选项模拟一遍栈的状态就能排掉三个。
2.4 组合计数:部门分组与捆绑法
第 3 题是部门选人:给定三个部门的人数,要求选出若干人组成工作组,每个部门至少一人。做法是枚举分布再乘组合数——先把名额分到三个部门,比如 (1,1,k)、(1,k,1)、(k,1,1) 这类分布,每种分布内部用组合数 C(部门人数, 该部门名额) 相乘,最后把各类分布的结果加起来。答题时容易错在重复计数:先确定分布形状,再算每类的方案数,最后相加,顺序不能乱。
第 14 题是三个女生必须相邻的排队问题,标准解法是捆绑法。把三个女生捆成一个整体,内部有 3! 种排法;这个整体再和其余男生一起全排列,若男生有 m 人,就是 (m+1)! 种;总数是 3! × (m+1)!。捆绑法的适用信号很明确:题干出现「必须相邻」「必须在一起」「作为一个整体」,就直接捆绑;反过来出现「不能相邻」,就用插空法,先排其他人再把不相邻的元素插进空隙里。
提示:组合计数题的选项往往数值差距很大,算出量级就能排掉两个,剩下的再精算,能省不少时间。
3. 阅读程序:40 分大题的逐行读法与三篇代码拆解
阅读程序是三篇代码配判断题和选择题,判断题 1.5 分、选择题 3 分,合计 40 分。这块的分值密度最高,也最容易被「凭感觉」毁掉。稳妥的读法是三步:先看函数名猜功能,再读 main 看输入输出,最后才逐行核对边界。函数名往往直接把答案写在脸上,比如 isPrime、countPrimes、sumPrimes,看到这三个名字就该知道程序在统计质数个数与质数和。
3.1 程序一:质数三件套的判断与陷阱
程序一给了三个函数:isPrime 判断质数、countPrimes 统计 1 到 x 的质数个数、sumPrimes 求 1 到 x 的质数和,main 里输出这两个结果。
bool isPrime(int n) {
if (n <= 1) return false; // 0 和 1 不是质数,这个边界必须留
for (int i = 2; i * i <= n; i++) { // 只试到 sqrt(n),靠 i*i<=n 避免浮点误差
if (n % i == 0) return false;
}
return true;
}
int countPrimes(int n) {
int count = 0;
for (int i = 2; i <= n; i++) if (isPrime(i)) count++;
return count;
}
i * i <= n 这个写法是关键,它等价于 i <= sqrt(n) ,但全程整数运算不会丢精度。改了它会不会影响结果,是这篇程序的核心考点。
第 16 题:输入 10,第一个输出是 4、第二个是 17。10 以内的质数是 2、3、5、7,个数 4,和 17,判断为正确。这题是送分的,但要注意「10 以内」包含 10 本身吗——10 不是质数所以不影响,如果输入的是 7 就要小心边界。
第 17 题:把 isPrime 的循环条件改成 i <= n/2 ,输入 20 时 countPrimes(20) 会变成 6。这是错误项。原因在于改条件只让判断变慢,不会让判断变错:一个合数的最小真因子一定不超过它的一半,所以试到 n/2 依然能筛出所有合数。20 以内有 2、3、5、7、11、13、17、19 共 8 个质数,输出仍是 8。这类题每年都出,本质是问「改了效率会不会改结果」,答案通常是不会,除非改动破坏了边界条件。
第 18 题:sumPrimes 计算的是 2 到 n 之间所有素数之和。正确,函数从 i=2 开始累加,跳过了 0 和 1。
第 19 题:输入 50,sumPrimes(50) 的值。50 以内质数和是 2+3+5+7+11+13+17+19+23+29+31+37+41+43+47 = 328。算不准就用估算:质数大约占 1/ln(50)≈1/3.9,数量约 13 个,平均值约 25,乘积 325 左右,对照选项只有 328 符合量级。
第 20 题:把 i*i <= n 改成 i <= n ,输入 10 时输出会怎样。答案仍是 4 和 17,只是运行变慢。这就是典型的「改条件不改结果」题,认准这一点能连对好几道。
3.2 程序二:动态规划题的识别信号
程序二的核心是一个 dp 数组和一个 min(dp[i-1], dp[i-2]) ,看到这个形式基本可以断定它是最小花费类动态规划,模型是「相邻两个位置必须选一个,求最小总花费」。
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
}
dp[i] 表示处理到第 i 个位置时的最小花费,转移时取前两步里较小的那个,再加上当前位置的代价。最后的 min(dp[n], dp[n-1]) 说明终点允许停在倒数第二格。
第 21 题:cost 为 {10,15,20} 时输出 15。手算:dp[1]=10,dp[2]=min(10,0)+15=15,dp[3]=min(15,10)+20=30,返回 min(30,15)=15。正确。
第 22 题:把 dp[i-1] 改成 dp[i-3] 会产生编译错误。错误。数组下标越界是运行期问题,编译器管不着,程序会正常编译然后跑出错误结果或者崩溃。这题的考点是「编译错误和运行错误的区分」——语法、类型不匹配才叫编译错误,越界、除零、死循环都算运行期问题。
第 23 题:程序总是输出 cost 数组中最小的元素。错误,它算的是满足约束条件下的最小和,不是简单取最小值。
第 24 题:cost 为 {1,100,1,1,1,100,1,1,100,1},求输出。逐个推:dp[1]=1,dp[2]=100,dp[3]=2,dp[4]=3,dp[5]=3,dp[6]=103,dp[7]=4,dp[8]=5,dp[9]=104,dp[10]=6,返回 min(6,104)=6。
第 25 题:cost 为 {10,15,30,5,5,10,20},同样的推法得到 dp 序列 10、15、40、20、25、30、45,返回 30。
第 26 题:把转移式改成 dp[i-1] + cost[i-2] ,cost 为 {5,10,15}。dp[1]=5,dp[2]=5+5=10,dp[3]=10+10=20,返回 min(20,10)=10。这种改动型题目一定要老老实实按新公式重算,别拿旧结论套。
3.3 程序三:递归展开到底在算什么
程序三的 customFunction 是 b == 0 时返回 a,否则返回 a + customFunction(a, b-1) 。手推一下就知道它把 a 加了 b+1 次,等价于 a × (b+1),最后 main 里还对结果取平方。
int customFunction(int a, int b) {
if (b == 0) return a; // 递归出口:还剩最后一层
return a + customFunction(a, b - 1); // 每层贡献一个 a
}
// customFunction(2,3) = 2+2+2+2 = 8,不是 2*3
看清这里有个常见的理解偏差:很多人以为是 a 乘 b,实际因为 b=0 时还要返回一次 a,总共加了 b+1 次。
第 27 题:输入 2 3,返回值为 64。错误,返回 8。题目问的是 customFunction 的返回值,不是 main 的输出,这两个数差一个平方。
第 28 题:b 为负数时会无限递归。正确,因为 b 永远减不到 0,递归没有出口。这是递归题的固定考点:只要出口条件覆盖不到参数的变化方向,就是死递归。
第 29 题:b 越大运行时间越长。正确,递归层数等于 b+1。
第 30 题:输入 5 4,customFunction(5,4) 的返回值。5 × (4+1) = 25。
第 31 题:输入 3 3,程序的最终输出。customFunction(3,3)=12,平方得 144。这题的关键是看清 main 里还有一层 pow(result, 2) ,问「返回值」和问「程序输出」是两个答案。
第 32 题:把函数改成 return a + customFunction(a-1, b-1) ,输入 3 3,求最终输出。展开:f(3,3)=3+f(2,2)=3+2+f(1,1)=3+2+1+f(0,0)=6+0=6,平方得 36。出口处 b=0 返回 a=0,这一步不能漏。
4. 完善程序:30 分填空靠结构识别,不靠灵感
完善程序是两个程序各 5 个空、每题 3 分共 30 分。这类题不需要你从零写出算法,需要的是看懂程序的骨架,然后判断每个空在该位置上「必须完成什么动作」。骨架判断准了,五个空基本能连对。
4.1 完全平方数:五个空对应五个动作
这个程序的骨架是:枚举可能的平方根 i,判断 i×i 是否等于 num。五个空分别对应初始值、上界、判断条件、命中返回、未命中返回。
bool isSquare(int num) {
int i = 1; // ① 从 1 开始枚举平方根
int bound = (int)floor(sqrt(num)); // ② 上界是 sqrt(num),向下取整
for (; i <= bound; ++i) {
if (num == i * i) { // ③ 判断 i 是不是平方根
return true; // ④ 命中即返回真
}
}
return false; // ⑤ 枚举完还没命中,返回假
}
第 33 题填 1。枚举平方根的下限必须是 1,填 0 会让 0×0=0 参与判断,虽然对正整数输入不致命,但语义上不对,而且很多变体题里输入可能含 0。
第 34 题填 (int)floor(sqrt(num)) 。上界取平方根的整数部分,向下取整保证不会漏掉 i×i=num 的情况。选项里带 num/2 的都是干扰项,上界取大了只会变慢,取小了会漏解,唯独 sqrt 是刚好。
第 35 题填 num == i * i 。注意必须是双等号做判断,单等号是赋值,这是选择题里的高频干扰项。
第 36 题填 true,第 37 题填 false。这两个空是配对的,一个在循环内命中时返回,一个在循环结束后返回,逻辑上必须一真一假。
4.2 汉诺塔:三步递归的参数顺序
汉诺塔的递归模板是三句话:把上面 n-1 个盘子从源柱搬到辅助柱、把最大的盘子从源柱搬到目标柱、把 n-1 个盘子从辅助柱搬到目标柱。五个空全在这个模板里。
void dfs(int i, char src, char tmp, char tgt) {
if (i == 1) { // ① 只剩一个盘子,直接搬
move(src, tgt); // ② 从源柱搬到目标柱
return;
}
dfs(i - 1, src, tgt, tmp); // ③ 上面 n-1 个先搬到辅助柱,此时目标柱充当临时柱
move(src, tgt); // 最大的盘子归位
dfs(i - 1, tmp, src, tgt); // ④⑤ 再从辅助柱搬到目标柱,源柱变成临时柱
}
第 38 题填 1。递归出口是只剩一个盘子的时候,此时直接搬。
第 39 题填 src, tgt 。递归出口处的搬运方向必须是从源柱到目标柱,不能反过来。
第 40 题填 src, tgt, tmp 。这一步是把 n-1 个盘子搬到临时柱上,所以原来的目标柱要临时充当辅助角色,三个参数的顺序是「源、临时目标、新辅助」。
第 41 题填 tmp, src, tgt 。最后一步从临时柱搬到目标柱,源柱变成辅助。
第 42 题填 i - 1 。递归规模每次减一。
参数顺序是这道题唯一容易翻车的地方,判断方法很简单:看这一步「盘子在哪个柱子上、最终要去哪个柱子」,第一个参数永远放盘子的当前位置,最后一个参数永远放它的目的地,中间那个是剩下的柱子。
4.3 填完必做的回代检查
五个空填完后,把答案代回代码,用最小规模手跑一遍。完全平方数代 num=9,看是否返回 true,代 num=8 看是否返回 false;汉诺塔代 i=1 和 i=2,看输出步骤数是不是 1 步和 3 步。这一步看着笨,但能抓住大部分参数顺序错误。血泪经验是:越觉得简单的空越容易填错,因为你会跳过回代直接下一题。
注意:完善程序的选项往往把「单等号和双等号」「i 和 i-1」「参数换位」做成干扰项,遇到这类选项先停三秒,确认位置语义再选。
5. 易错点:五个每年都有人踩的失分点
5.1 否定题干看漏,会做的题也白丢
错在哪:题干问「以下哪个不是」「不属于」「错误的是」,读题时自动过滤成「哪个是」,选了一个正确项就交卷。第 6 题的 struct、第 7 题的 repeat-until、第 10 题的非操作系统选项,全是这种问法。
为什么错:大脑在熟悉场景下会自动补全语义,看到四个选项里三个眼熟,就默认在问「哪个对」。而且这类题通常排在前面,做题状态还没热起来。
怎么答对:读题时用笔把「不」字圈出来,选完之后回头再读一遍题干,确认自己选的选项符合「不是」这个限定。这动作只花三秒,能救 2 到 4 分。
5.2 以为改了循环条件结果就会变
错在哪:看到「把 i*i<=n 改成 i<=n/2 」就默认输出会变,直接判错或选了一个不同的数值。第 17 题和第 20 题都是这个套路。
为什么错:混淆了「效率」和「正确性」。循环条件放宽只影响执行次数,只要还覆盖了所有可能的因子,判断结果就不变。真正会改变结果的是边界被截断,比如把上界改到 sqrt 以下。
怎么答对:看到「修改条件」类判断题,先问一句「这个改动会不会漏掉某个必要的情况」。如果只是多跑几轮,答案就是不变;如果上界缩小到可能漏解,答案才变。这个判断标准对质数判断、枚举查找类程序都适用。
5.3 混淆「函数返回值」和「程序最终输出」
错在哪:第 27 题问 customFunction(2,3) 的返回值,很多人算对了 8,但看到题干写 64 就犹豫;第 31 题问程序最终输出,又有人只算到 12 就选了。同一篇程序里,问的是哪一层必须看清楚。
为什么错:main 函数里套了一层 pow(result, 2) ,题目问「返回值」时不含平方,问「输出」时含平方。两层结果差一个平方,选项里通常两个都有。
怎么答对:读题时把「返回值」「输出」「函数执行结果」这三个词分别标出来,返回值只看函数体,输出必须走到 main 的最后一行。养成在草稿上写两行字的习惯:返回值 = ?,输出 = 平方后的值。
5.4 编译错误和运行错误分不清
错在哪:第 22 题「把 dp[i-1] 改成 dp[i-3] 可能会产生编译错误」,不少人凭「这看着就不对」选了正确。
为什么错:把「程序会出错」笼统等同于「编译不通过」。数组下标越界、负数下标、除零、死递归,都是代码能编译但运行时出问题;只有语法错误、类型不匹配、未声明变量才是编译期报错。
怎么答对:记住一条分界线——编译器只检查「写法是否符合语法」,不检查「值是否合理」。凡是涉及下标、数值范围、递归层数的,一律归到运行期。单选题里遇到「编译错误」这个选项,先怀疑它是干扰项。
5.5 完善程序里参数顺序填反
错在哪:汉诺塔的第 40 题填成 src, tmp, tgt ,看着和函数签名一致,实际上把目标和辅助搞反了,后续步骤全错。
为什么错:填参数时按「函数定义里怎么写的」去套,而不是按「这一步实际要做什么」去推。递归调用的参数顺序取决于当前步骤的搬运目标,和函数签名没关系。
怎么答对:每个递归调用前先在心里说一句「现在要把 i-1 个盘子从哪搬到哪,剩下的那根柱子做什么」,然后按「起点、临时柱、终点」的顺序填。填完用 i=2 手跑一遍,两步之内就能发现顺序错没错。
6. 押题与节奏:100 分怎么在 90 分钟里切块
真到考场上,比会不会做更影响分数的是时间分配。给一个可执行的切法:
| 阶段 | 时间 | 任务 | 目标 |
|---|---|---|---|
| 第一遍 | 0~25 分钟 | 15 道单选全部做完,不会的标记跳过 | 拿到 24 分以上 |
| 第二遍 | 25~65 分钟 | 三篇阅读程序,先读函数名再逐题判断 | 拿到 30 分以上 |
| 第三遍 | 65~85 分钟 | 两道完善程序,先填有把握的空 | 拿到 24 分以上 |
| 收尾 | 85~90 分钟 | 回看标记题,检查否定题干和单位换算 | 补回 4 到 6 分 |
按这个节奏,选择题每道平均不到两分钟,阅读程序每题三分钟左右,完善程序每个空三分钟。超出时间就跳,别在一道判断树上死磕。
押题方向上,几个考点几乎是年年出现:存储单位换算里「字节乘 8」这一步、循环条件改动是否影响结果、递归出口与无限递归、二叉树的前中后序互推、栈的出栈序列判断、组合计数的捆绑与插空。这六类占了卷面一半以上的分值,考前把这几类的真题各刷五道,比刷一百道偏题有用。
还有一个容易被忽略的检查动作:所有涉及「不是」「至少」「最多」「不超过」的题干,交卷前统一回看一遍。这些限定词决定了答案的边界,翻车的往往不是不会,而是看漏。
某次模考里,我把「以下哪个不是基本数据类型」看成了「哪个是」,选完还觉得题目太简单,出分才发现整道题白送。从那以后我每次做完选择题,都强制走一遍「题干限定词回读」的流程,把每道题的否定词和范围词重新读一遍才翻页。这个习惯看着笨,但它把会做的分稳稳锁住了。希望帮到你。
本文系统解析CSP-J 2024初赛真题,聚焦单项选择题(30分)、阅读程序题(40分)和完善程序题(30分)三大模块。涵盖int存储范围、格雷码、存储单位换算、C++基本类型与循环语句、ASCII码、二分查找、图论度数和、二叉树遍历、栈操作、动态规划识别、递归分析、完全平方数判定、汉诺塔递归结构等关键信息技术考点,并强调编译错误与运行错误区分、函数返回值与程序输出差异等高频易错点。
4964

被折叠的 条评论
为什么被折叠?



