多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

CSP-S初赛阅读程序题剖析:字符串统计计数与常见误区

CSP-S初赛阅读程序题剖析:字符串统计计数与常见误区 每年初赛备考阶段总有同学拿着阅读程序题来问我代码明明不长怎么一对答案就错如果你也有这种感觉我建议先把2019年CSP-S初赛阅读程序第1题拿出来拆一遍。这题在当年整个初赛试卷里属于“放分题”字符串长度不超过100只做一个字母计数代码也不到30行。可就是这么一道题每年讲评时都得花一节课因为错法太集中了——有人没看清楚统计范围有人忽略了并列时的字母序规则还有人直接把数组下标算到越界。这篇博文就把这题完整拆开从题目结构、核心代码到手算推理、常见误区最后说说阅读程序这一类题怎么系统复习。无论你是刚开始练C的小白还是备考CSP-S的老选手都可以照着这个思路过一遍。1. 2019年CSP-S初赛阅读程序第1题到底在考什么1.1 先说清楚阅读程序在初赛中占多少分量CSP-S第一轮满分100分试卷一般由单选题、阅读程序、完善程序三大块组成其中阅读程序通常会有4道大题每道大题下面再设置判断题、选择题或者直接要求写出输出结果的小题。满分100分的卷子里阅读程序加起来约占40分这个比重相当可观。很多选手备考时注意力全放在完善程序上觉得那才是真正的拉分项。但以我带学生的经验来看阅读程序才是区分度最稳定的一块单选题不会还能蒙一个完善程序没把握时瞎填基本拿不到分反倒是阅读程序题只要掌握稳定的手算方法分数相对好拿。而阅读程序第1题又是整套题里最简单的一道属于“基础中的基础”。如果连这道题都丢分后面的题目会非常被动。这道题考的东西并不高深字符数组、ASCII码、循环、条件判断、数组计数全是C入门就要反复练的基本功。但它对“细节”的要求很苛刻命题人想测试的其实是你能不能像计算机一样一步步执行代码而不是凭感觉“看懂”代码。1.2 命题人在简单题里埋的三个常规陷阱越是看起来简单的阅读程序越容易藏细节。2019年这道字符串统计题概括起来就是在输入串中统计小写字母出现次数然后输出出现次数最多的字母和次数。逻辑不复杂但命题人至少埋了三个坑。第一个坑是统计范围。程序只统计小写字母输入中的大写字母、数字、标点符号全部被忽略。很多考生模拟的时候想当然把所有字符都算进去结果答案偏掉了一大截。第二个坑是并列规则。如果多个字母的出现次数相同到底输出哪一个这取决于程序中比较条件的写法是“大于”更新还是“大于等于”更新结果完全不同。第三个坑是边界情况。如果输入串里一个合法字符都没有程序会不会崩会不会什么都不输出答案都不是它会输出一个看起来不太合理的“a 0”。这三个坑只要有一个没想清楚选项直接就偏了。而这三个坑的解法都不需要高级算法靠的全是逐行读代码、老实做模拟。1.3 一个重要提醒这是“考生回忆还原版”先说明一个问题信息学初赛的原卷一般不会完整公开网上流传的2019年CSP-S初赛真题基本都是考生回忆加机构整理出来的版本。不同版本之间个别字符范围、数据规模可能有细微差别但核心代码逻辑和考查点是稳定一致的。所以这篇文章里的代码我按流传比较广的还原版来解析重点放在“面对这类题应该怎么推理”而不是让你背下某一个版本的答案。考试不会原样复现这道题但解题方法完全可以迁移到新的阅读程序题上。看完这篇文章你再去刷其他年份的真题会发现很多题的内核是相通的。2. 还原版题目与核心代码逐行拆给你看2.1 题目与代码长什么样先还原一下题目形式。这是一道“阅读程序写结果”大题题干会给你下面这段代码然后要求你回答几个小题比如判断题若输入为abracadabra程序输出a 5。对还是错选择题这段程序的主要功能是什么选择题若将判断条件改为统计大写字母且输入为HelloWorld2023程序输出什么选择题若输入字符串为zoo程序输出什么代码是下面这样的#include iostream #include cstring using namespace std; int main() { char s[105]; int cnt[26] {0}; cin s; int len strlen(s); for (int i 0; i len; i) { if (s[i] a s[i] z) { cnt[s[i] - a]; } } int best 0; for (int i 1; i 26; i) { if (cnt[i] cnt[best]) { best i; } } cout char(a best) cnt[best] endl; return 0; }输入约定是一个不含空格的字符串长度不超过100。程序最后会输出一个字母、一个空格、一个数字再换行。2.2 逐行读代码每一行到底在干什么先看头文件和主函数结构。#include iostream负责输入输出#include cstring是为了使用strlen函数。char s[105]定义了一个字符数组长度105比题目限制的100多留了5个位置防止边界溢出。int cnt[26] {0}定义了一个整型数组下标范围0到25正好对应26个小写英文字母初始化成0。cin s的作用是从标准输入读取一个字符串存进字符数组。它遇到空格或者换行就会停止所以输入串里不能包含空格。int len strlen(s)拿到的是字符串有效长度也就是实际字符个数注意strlen不包含结尾的\0。第一个for循环从头到尾扫描每个字符。if条件s[i] a s[i] z是用来过滤的只有小写字母才会进入计数逻辑。cnt[s[i] - a]是整道题最关键的一行把字符转换为对应的数组下标然后让那个位置上的计数器加1。第二个for循环从下标1扫到25不断比较cnt[i]和cnt[best]。best记录的是当前出现次数最多的字母下标初始值设成了0也就是字母a。注意更新条件是而不是这意味着并列最多时下标更小的字母会保留下来。最后输出时char(a best)把下标还原成字母后面再输出对应的计数次数。2.3 核心考点字符到数组下标的连续映射为什么s[i] - a能作为数组下标这里依赖的是ASCII码的连续排列。在ASCII码表中a的值是97b是98依次递增到z是122。所以b - a的结果是1z - a的结果是25恰好可以映射到cnt[1]和cnt[25]。这种“字符转下标”的写法在信奥赛里非常常见字符串处理题几乎绕不开。反过来当你想从下标还原字母时就用char(a best)。很多同学在这个问题上翻车不是不会写if而是没想清楚映射关系手算的时候把s[i]本身当成了下标甚至写成cnt[s[i]]模拟着模拟着数组越界结果自然是错的。这里我多说一句竞赛环境里数组越界不一定报错很多时候程序“看起来正常”但结果已经不对了。阅读程序题考的就是你能不能在人脑里预判这种错误所以对下标的取值范围一定要敏感。2.4 最容易被忽略的输出细节和并列结果输出语句是cout char(a best) cnt[best] endl;这里有几个细节值得注意。第一输出的是字母和数字中间有一个空格第二输出完会换行第三best代表的是下标不是ASCII码所以必须用char(a best)把它转换回字符。如果你在纸上模拟时直接把数字当成ASCII码输出答案会差得很远。并列规则也需要单独拿出来说。假设出现了两个字母出现次数都是3比如字母c和字母o。程序从下标1开始扫描遇到下标2也就是c时cnt[2]等于3比当前cnt[best]大于是best更新成2等扫描到下标14也就是o时cnt[14]也等于3但条件要求严格大于3不比3大于是best保持不变。所以最终的结论是出现次数并列最多时输出ASCII码最小的那个字母也就是字典序最靠前的字母。这个结论不是靠背下来的而是从这个比较符号里推出来的。以后看到类似的“找最大”代码先扫一眼更新条件是还是这一步能帮你避开大量失分点。3. 考场上的完整推理流程一道题吃透“手算代码”3.1 拿到阅读程序题先做这三步很多同学一上来就从第一行开始逐行翻译读到一半就晕了。我的习惯是先看框架再抠细节。第一步先找输入语句和输出语句明确这个程序接收什么、输出什么。第二步看核心数据结构比如这里的cnt[26]明显是用来计数的s[105]是存字符串的。第三步在脑海里把这个程序的功能压缩成一句话统计小写字母出现次数输出出现次数最多的字母和它的次数。这个“一句话概括”非常重要。考场时间紧张你不可能每次都完整模拟几十个字符的走向但如果你能快速概括出程序的功能很多选择题直接就能作答根本不需要一步步算。判断题和功能描述题尤其吃这一套。当然概括功能的风险在于容易忽略细节。所以接下来还是要挑一个输入样例老老实实做一遍手算。3.2 用表格跟踪变量以abracadabra为例模拟输入串abracadabra。我先不急着画十几行表而是先统计每个字符出现的次数。这个串一共有11个字符逐个拆开a、b、r、a、c、a、d、a、b、r、a。统计结果是a出现5次b出现2次c出现1次d出现1次r出现2次。用表格表示下标字母出现次数0a51b22c13d117r2其他字母对应的计数全是0。接下来看第二个循环。best初始为0也就是a次数5。扫描过程中b的次数2不大于5c的1不大于5d的1不大于5r的2也不大于5所有比较都不满足更新条件所以best始终是0。最终输出就是a 5。这个结果很直观但要注意这里a是最多的字母所以并列规则没有触发难度主要在后面那些变式题里。3.3 改造版的完整推理条件改成大写字母会怎样现在把if条件从s[i] a s[i] z改成s[i] A s[i] Z也就是只统计大写字母输入改成HelloWorld2023。这时候数字和小写字母全部被忽略能被计数的只有大写H和W各出现1次。下标计算是H - A等于7所以cnt[7]加1W - A等于22所以cnt[22]加1。扫描的时候best初始是0对应cnt[0]值是0。从i 1开始前面一堆下标对应的次数都是0不满足更新条件一直到i 7时cnt[7]等于1大于cnt[best]的0于是best 7。到i 22时cnt[22]也等于1但1不大于1不更新。所以输出是H 1而不是W 1。这个例子完美展示了“并列取最小下标”的规则两个大写字母出现次数相同谁在字母表里更靠前谁就是输出对象。这种改造版题目很常见命题人会把统计范围从大写换到小写或从字母换成数字本质不变但细节一变答案就完全不一样。3.4 考试时这类题应该花多少时间阅读程序第一道大题正常情况下5到8分钟是足够的。如果一道简单的字符串统计题你卡了超过10分钟大概率是某个细节没有想通比如统计范围看错了或者对并列规则理解反了。这时候我的建议是先跳过做后面的题回头再检查。初赛整体时间其实很紧完善程序比阅读程序更耗脑力选择题里还有进制转换、排列组合这些容易算错的内容。在一道本应该“放分”的题上死磕性价比很低。你真正要做的是平时把模拟步骤练熟让手算变成肌肉记忆考场上才能又快又稳。4. 阅读程序常见失分点与自查清单4.1 失分点一判断条件边界写错这类统计题最典型的问题是条件边界。比如把s[i] a s[i] z看成s[i] A s[i] z这一改所有大写字母也会被误判成“合法字符”统计结果直接错掉。还有同学会把a和z记反写成s[i] z s[i] a这种低级错误一旦在考场上出现整道题的结论全盘皆输。我的检查习惯是在做模拟之前先在代码里圈出影响统计范围的判断条件明确它到底允许哪些字符进入计数。这一步只花10秒钟却能防止后面整个模拟过程偏离方向。4.2 失分点二并列规则理解反了第二个高频失分点是并列规则。很多同学凭直觉认为“两个字母次数一样就输出后面出现的那个”但程序实际输出的是字典序靠前的那个因为更新条件是严格大于。直觉经常害人尤其在条件符号这种细枝末节上。如果你拿不准教你一个笨但可靠的办法构造一个并列的极端输入比如aabb。这个输入里a和b各出现2次程序输出应该是a 2而不是b 2。在草稿纸上跑完这个用例并列规则自然就清楚了。考场上遇到含糊的选项构造成串极端输入去验证是性价比最高的自救手段。4.3 失分点三没注意输出格式和初始化输出格式看着不起眼错了照样扣分。比如题目要求输出“字母 空格 次数”有的选项故意写成“次数 空格 字母”或者中间没有空格。手算对了选项选错了这是最冤枉的失分方式。所以每次模拟完一定要把最终输出语句再读一遍确认输出顺序和分隔符。初始化也是一个隐蔽的坑。代码里int cnt[26] {0}明确把所有计数器清零了但如果某个还原版本里写成int cnt[26]没有初始化局部变量的初值就是不确定的模拟结果会变成随机数。考场上看到数组定义时多问自己一句这个数组初始化了吗初始值是什么4.4 阅读程序自查清单做题前过一遍我把这类题目常踩的坑整理成一个清单每次做完阅读程序题按这个顺序自查一遍检查点应该问自己的问题统计范围统计的是大写、小写还是所有字符数字算不算下标映射字符怎么转成下标会不会越界范围是多少更新条件找最大值用的是还是并列时输出哪个输出格式输出几个值顺序是什么有没有空格和换行边界输入空串、全无关字符、全同字符的输入会输出什么初始化数组有没有清零变量初值是什么这个清单不只是拿来检查这一道题而是几乎所有阅读程序通用。用顺了之后读代码时会自动带着这些问题去扫效率比盲目逐行翻译要高很多。5. 从这一题出发CSP-S阅读程序还能怎么复习5.1 把“三步法”变成你自己的答题框架读完这道题你应该已经感受到阅读程序本质上是一种“人肉CPU”训练你要在脑子里按照代码逻辑一步步执行同时还要预判边界情况。我把它总结成三步先看输入输出和数据结构概括出程序功能然后挑一个典型输入做手算模拟最后用极端输入验证结论。这个方法几乎适用于所有阅读程序题。比如遇到递归代码就先画递归调用树遇到排序代码就盯住每一轮交换的边界遇到位运算就老老实实把二进制写在草稿纸上。不要只在这一道题上用后面刷真题时每一道都按这个流程走练到形成条件反射。5.2 阅读程序的高频考点清单从历年初赛来看阅读程序题的高频考点相对固定字符串处理是最大的一块包括大小写转换、字符计数、子串匹配接着是进制转换和位运算比如十进制转二进制、按位与或异或然后是数学问题比如质数判断、最大公约数、辗转相除法排序算法也常考冒泡、选择、插入排序的交换次数和比较次数是出题人最喜欢问的递归和基础数据结构模拟比如栈的出入顺序、队列的进出队过程也经常出现。另外还有一类是随机数rand()配合取模运算生成某个范围内的数。注意只要种子固定随机数序列就是可以预测的这类题考的其实是取模映射范围。5.3 刷题建议和工具选择刷阅读程序题最忌讳“看解析觉得自己会了”。正确做法是先把题打印出来在纸上独立手算写出答案之后再对着解析核对。我见过太多学生眼睛看懂了但一动手就漏洞百出原因就是缺乏这种“手算肌肉记忆”。有条件的话可以把历年真题的代码敲到本机环境里跑一遍对比手算结果和运行结果。环境方面用Dev-C或者VS Code配置C/C环境都可以代码不长跑起来很快。跑完之后刻意改一两个条件试试比如把改成把统计小写改成统计大写看输出怎么变。这种“改题练习”比单纯刷题更能帮你理解代码的每一个细节。最后说点个人体会。带过好几届选手我发现阅读程序题从来不是靠聪明而是靠稳定的答题流程。2019年这道第1题放到现在看代码简单得像练习题但每年都有学生栽在细节上说明越是简单的题越要按流程走。建议你把“三步法”用在接下来刷的每一道真题上练到不需要思考就能执行。最后再分享一个小技巧做完阅读程序题先别急着对答案花10秒钟在草稿纸上构造一个极端输入比如全是同一个小写字母或者完全没有合法字符心算一遍输出。这个习惯能帮你挡掉至少一半的坑亲测有效。
返回列表