简介:2024年海淀区中小学生信息学竞赛校级预选赛试题,面向海淀区参赛中小学生及指导教师,用于赛前自测、校内选拔和考点梳理,内容紧扣区级预选赛的命题要求。资源为1个PDF文档,资源包仅422KB,卷面完整,适合直接打印作答或电子屏幕阅读。试卷分为编程基础知识单选与程序阅读单选两部分,编程基础题围绕合法变量名、赋值语句写法、二进制与十进制转换、表达式与逻辑判断、函数定义与递归、运算符、循环及break/continue使用、数组等展开;程序阅读题则通过求因数个数、公约数与最小公倍数、回文数判断、素数筛选等经典案例,要求考生根据给定代码推断输出结果或特定变量值,全面考查代码跟踪能力和算法理解水平。已有575人学习浏览,学生借助本卷可系统检验C++基础掌握情况,教师也可快速掌握学情,进行针对性讲评与拓展训练。
1. 拿到2024年海淀区中小学生信息学竞赛校级预选赛试题:先别慌,它的套路比想象中固定
2024年海淀区中小学生信息学竞赛校级预选赛试题1103.pdf,如果你正在对着这份卷子备赛,说明你站到了区级信息学选拔的入口上。校级预选赛的定位不是选天才,而是筛态度和基本功:题目难度不高,但考察点很固定,C++基础语法熟练度、模拟题的拆解能力、数学模板的掌握程度,这三样决定了你能不能从预选赛里走出来。许多零基础同学把这卷子当成黑匣子,其实它最常出现的题型就三类——模拟、数学、数组字符串,提前把它们练透,及格线基本到手。
接下来的内容我会按“题型识别 → 备考节奏 → 踩坑排查 → 临场流程”四段展开。新手可以跟着从第一道模板题写起,已经会写基础程序的同学可以直接跳到避坑章对照检查。读完之后你应该能回答三个问题:这份卷子到底在考什么,每天该练什么,交卷前有哪些动作是白给的。
2. 校级预选赛的三大题型拆解:模拟、数学、数组字符串怎么学才算到位
信息学竞赛的区级预选赛,出题人有很强的一致性:题目数量通常控制在四到六道,分值由易到难排列,第一道题基本是送分模拟,最后一道题会掺进数组和排序。这和市级、省级比赛那种“考思维爆发力”的风格不同,预选赛更像是在问一个问题:你有没有把基础语法练到不用想就能写对的程度。
所以我不建议在这个阶段去学什么线段树、动态规划、图论算法,那些东西对预选赛来说属于高射炮打蚊子。真正值得投入时间的是三类题型的固定套路。
2.1 模拟类题型:读题三步法,先把边界变量单独拉出来
模拟题是预选赛里出现率最高的题型,也是新手失分最多的地方。它的特征很直观:题目描述会把规则讲得明明白白,比如“从某个位置开始每天前进多少格”“某年某月某日是星期几”“给一个字符串做循环移位”,你只需要把规则原样翻译成循环和判断,就能拿到分。听起来不难,但实际考场上,很多人输在漏看条件。
常见的备考误区是拿到题直接上手写循环,写到一半才发现边界条件没处理,于是开始打补丁,补到最后代码变成一团乱麻。我一般会让学生先做三步:第一步把输入的数据范围圈出来,第二步把所有会变化的边界写成短列表,比如行数、列跨度、起始和结束位置,第三步再动手写代码。图形打印题是最典型的模拟题,也是每年预选赛都绕不开的考点。
#include <iostream> using namespace std; int main() { int n; cin >> n; for (int i = 1; i <= n; i++) { // 第 i 行先输出 n-i 个空格 for (int j = 1; j <= n - i; j++) cout << ' '; // 再输出 2*i-1 个星号 for (int j = 1; j <= 2 * i - 1; j++) cout << '*'; cout << '\n'; } return 0; }这段代码的逻辑很直白:外层循环控制行数,内层第一个循环控制空格,第二个循环控制星号。关键在 i 与行号的对应关系上,如果你能把 n=5 时每一行的空格数和星号数先在草稿纸上列出来,再回头对照代码,循环边界基本不会错。
如果两分钟内写不出这段代码,说明循环控制还不够熟,建议先把行数、空格数、星号数三层关系练到条件反射。预选赛里的模拟题比这个绕的地方在于,它常把二维数组和操作组合在一起,比如矩阵翻转、蛇形填数,这类题的核心还是边界变量要提前算好,不能写一步试一步。
2.2 数学类题型:质数、最大公约数与取模模板
数学题在预选赛里的占比通常在两到三成,而且规律性极强。翻来覆去就是质数判断、最大公约数、最小公倍数、取模运算这几类,偶尔会套一个递推数列的外壳,但内核仍然是你背过的模板。数学题对程序运行时间的敏感度很高,如果不懂复杂度,很容易写一个能过样例却在大数据上超时的版本。
质数判断是最常见的送分题,核心是枚举到根号 n 就停,不需要从 2 试到 n 本身。
#include <iostream> using namespace std; bool isPrime(int x) { if (x < 2) return false; for (int i = 2; i * i <= x; i++) { if (x % i == 0) return false; } return true; } int main() { int n; cin >> n; cout << (isPrime(n) ? "Yes" : "No") << '\n'; return 0; }这段代码要注意两个细节:一是 x 小于 2 的情况要单独返回 false,二是循环条件用i * i <= x而不是i <= sqrt(x),后者每次循环都要重新算一次浮点开方,在预选赛数据量下虽然差别不大,但坏习惯最好从一开始就别养成。如果数据范围很大,还要考虑用埃氏筛一次性筛出区间内所有质数。
取模类题目是另一个高频点,常见考法是给出一个递推公式,让求第 n 项模某个数。这类题的陷阱在于计算过程中间值可能溢出,新手往往只对最终结果取模,忽略了乘法中间步骤。
#include <iostream> using namespace std; int main() { long long a, b, mod; cin >> a >> b >> mod; long long ans = 1; while (b > 0) { if (b % 2 == 1) ans = ans * a % mod; a = a * a % mod; b /= 2; } cout << ans << '\n'; return 0; }这是快速幂的模板,用来求 a 的 b 次方模 mod。核心思想是把指数拆成二进制,每次对底数平方,只在当前二进制位为 1 时乘进答案。你不需要完全理解它的数学证明,但要记住两个使用场景:指数 b 特别大,或者中间乘法会超过 long long 范围。预选赛里只要看到“结果对 1000000007 取模”这种描述,基本就是在暗示你用这类模板。
数学题拿分的捷径是整理错题本,不是刷题量。把见过的公式和模板抄在一张纸上,考前半小时过一遍,比临时翻书管用得多。
2.3 数组与字符串题型:前缀和、排序和二分
数组和字符串题是预选赛的压轴地带,难度比前两类高一个台阶,但题型也是固定的。这里最常考的是三件事:前缀和、排序、二分查找。它们经常组合出现,比如先排序,再对每个询问做二分;或者先算前缀和,再快速回答多个区间求和问题。
前缀和的代码极短,但它解决的是“多次询问区间和”这类高频问题,如果你不知道这个套路,会写一个每次询问都重新遍历的暴力程序,数据一变大就超时。
#include <iostream> using namespace std; long long a[100005], pre[100005]; int main() { int n, m; cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; pre[i] = pre[i - 1] + a[i]; } cin >> m; while (m--) { int l, r; cin >> l >> r; cout << pre[r] - pre[l - 1] << '\n'; } return 0; }这段代码里最容易忽略的是pre[i] = pre[i - 1] + a[i]这一句,它把前 i 项的和存下来,之后任意区间 [l, r] 的和就是pre[r] - pre[l - 1]。我故意把数组类型写成了 long long,因为如果 n 超过十万、a[i] 再大一些,区间和很容易超过 int 的 21 亿上限,这是预选赛最经典的爆零点之一。
排序加二分的组合题,主要考察sort和lower_bound的配合。常见考法是给你 n 个数字,然后来 m 个询问,每个询问问某个值是否存在,或者第一个大于等于它的位置在哪。直接用排序加二分能把复杂度压到 O((n+m)logn),而暴力做法是 O(nm),在 n 和 m 都过万时,运行时间会从毫秒级变成几十秒。
数组和字符串题拿高分的另一个关键是做题时要对下标敏感。预选赛经常出“下标从 1 开始”的题,因为这样写前缀和和差分更直观。如果你习惯下标从 0 开始,写前缀和时要么统一改掉,要么在草稿上画清楚对应关系,这种一差之错往往要排查很久。
3. 从零基础到预选赛过关:8周备考节奏与一次性写对的检查链
确定要参加预选赛后,最怕的不是能力不够,而是时间安排混乱。很多同学今天学两小时排序,明天又去刷字符串,后天发现语法全忘了,回头重新补,到头来每样都只学了半桶水。我给备考者推荐的节奏是八周,前四周只练语法和基础,中间两周过模拟和数学题,最后两周进入套题和模拟考状态。
这个节奏的核心原则是“先练到不用想,再练难题”。预选赛考场上每一分钟都宝贵,如果连 for 嵌套循环都要现场想怎么写,那后面的大题一定来不及。
3.1 8周时间线:C++语法要练到哪个程度再刷题
我用一张表把每周的重点列出来,你可以直接照着安排:
| 周数 | 学习重点 | 达标标准 |
|---|---|---|
| 第1周 | 变量类型、cin/cout、if/else | 能写一个根据分数输出等级的程序 |
| 第2周 | for循环、while循环、嵌套循环 | 能不查资料写出各种三角形打印 |
| 第3周 | 一维数组、字符数组、string | 能写出统计字符串中字母数字个数的程序 |
| 第4周 | 二维数组、函数封装 | 能实现矩阵转置,并封装成函数 |
| 第5周 | 模拟题专项训练 | 连续做10道模拟题,正确率不低于六成 |
| 第6周 | 素数、gcd、快速幂模板 | 能默写出三个模板并说明适用场景 |
| 第7周 | 排序、二分、前缀和专项 | 能独立完成“排序+二分”的组合题 |
| 第8周 | 完整套题模拟 | 限时2小时完成一套模拟卷,稳定在及格线以上 |
前四周是地基,千万别赶。语法没练透就去碰算法,后面写出来的代码经常是运行结果和预期不一致,又说不清问题出在哪,只能瞎试,这就是编程里的“玄学调试”。
我在教学中反复强调一个标准:一个知识点,你要做到关着书能写出来,才算真正会。C++语法的记忆量不大,但细节很多,比如++i和i++在表达式里的区别、字符串下标从 0 开始、浮点数不能直接用==比较,这些坑在前三周暴露得越多越好。
3.2 一道模拟题的完整求解流程:从读题到写出能提交的代码
很多人刷题时习惯读题两分钟然后立刻写代码,这是一个坏习惯。我在第 2 章提过读题三步法,这里我把完整流程展开,用一道数字三角阵的典型题走一遍。题目描述长这样:输入正整数 n,要求输出 n 行,第 i 行输出从 1 开始连续递增的 i 个数,相邻数字用空格隔开,行末没有多余空格,每行结束后换行。
先做读题三步:输入是一个正整数 n,输出是 n 行数字,行数是唯一的边界变量,当前要输出的数字是另一个状态变量。想清楚这两点,代码结构就可以直接搭出来。
#include <iostream> using namespace std; int main() { int n; cin >> n; int cur = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { cout << cur++; if (j != i) cout << ' '; } cout << '\n'; } return 0; }这里有一个细节经常被忽略:每行行末不能有多余空格。有没有发现我在输出空格时加了if (j != i)判断,只有当当前列不是最后一列时才输出空格,这样就保证了行尾没有空格。这种格式化细节在判题系统里往往会变成 Presentation Error,虽然不算完全零分,但浪费了一次提交机会,有时候还会被考场规则扣分。
写完代码不要急着提交,先用手算验证 n=3 时的输出:第一行是 1,第二行是 2 3,第三行是 4 5 6,然后和程序输出对比。这一步看似多余,实则是防止你把边界算错的最笨也最有效的方法。
3.3 本地对拍:用暴力程序当裁判,验证优化思路不翻车
进阶备考者经常遇到一个问题:我写了一个自认为高效的解法,但不确定它对不对。这时候玄学猜答案不如写一个对拍程序。对拍的意思是,写一个效率低但逻辑绝对简单的暴力程序作为标准答案,再用随机数据同时跑你的优化程序和暴力程序,逐个对比输出。
预选赛题目数据范围通常不会太大,暴力程序完全能跑出正确答案。对拍能帮你验证优化程序在小数据上是否和暴力结果一致,如果不一致,说明优化思路在某个边界条件下有问题。
#!/bin/bash # 对拍脚本:生成随机数据,比较两个程序的输出 for i in $(seq 1 1000); do ./gen > input.txt ./my_solution < input.txt > my_out.txt ./brute_force < input.txt > brute_out.txt if ! diff -q my_out.txt brute_out.txt > /dev/null; then echo "WA on test $i" break fi done echo "all tests passed"这个脚本做的事情是循环一千次,每次先用gen程序生成一组随机输入,然后分别运行my_solution和brute_force,用diff比较输出。只要有一次不一致,就打印WA on test并停止,否则打印all tests passed。
gen程序是你自己写的随机数据生成器,它要保证数据范围覆盖题目的边界值,比如 n=1、n=1000000、数组里有负数等情况。对拍框架本身不复杂,但它是备赛过程中最能帮你建立信心的一步。很多翻车场景是“优化程序跑小数据对,跑大数据错”,对拍就是为了让小数据先替你把所有边界踩一遍。值得注意的是,对拍脚本要在本地命令行环境里跑,你需要会简单的编译操作,比如g++ gen.cpp -o gen。
3.4 定位边界错误:先看输入输出格式,再用中间变量追踪
程序写出来但输出不对,第一步不要改代码,而是先检查输入输出格式是否和题目要求一致。预选赛里相当一部分“WA”其实是读错了输入,比如题目说每组数据用换行分隔,你却用了空格。
如果格式没错,就要在代码里加中间变量的输出,把你认为应该在某个阶段出现的值打印出来看一眼。这是最朴素的调试方法,但它对新手最友好。
#include <iostream> using namespace std; int main() { int n; cin >> n; for (int i = 1; i <= n; ++i) { // 调试语句:观察当前行号和累计输出数量的变化 cerr << "debug: i=" << i << ", cur=" << cur << endl; for (int j = 1; j <= i; ++j) { cout << cur++; } cout << '\n'; } return 0; }注意这段代码我用的是cerr而不是cout,因为cerr在判题系统里通常不会计入标准输出。本地运行时它会把调试信息打印到终端,提交时你删掉这些行也不会影响最终结果。调试语句的作用是让你把程序在每一个关键节点的状态可视化,而不是靠肉眼去猜是哪一行出了问题。
如果加了调试语句还是找不出问题,就把代码放一边,在草稿纸上手动跑一遍小数据,一步步写出当前变量的值。人脑按顺序执行代码比编译器调试器更直观,对小规模数据来说,这个方法基本百试百灵。
4. 预选赛踩坑记录:五条血泪经验让翻车概率降下来
这一章是过去几年我看到的考生最容易踩的坑,每一条都有真实的翻车案例。这里我按“现象 → 原因 → 解决”的顺序写,你可以把它当成一个自查清单。如果你的程序在本地跑起来正常,提交却各种报错,很可能就落在下面五条里。
4.1 int溢出:样例全过、提交爆零的典型场景
现象:本地用样例数据测试,结果完全正确,但提交到线上评测系统,在小数据上也不对,或者只有一部分数据对。你反复检查逻辑,怎么都想不通。
原因:你的程序里某个变量的中间值超过了 int 的表示范围。int 在绝大多数环境下是 32 位,最大只能表示 2147483647,约 21 亿。前缀和、累乘、快速幂这种运算,中间值很容易轻轻松松破亿。
解决:把所有可能参与大数运算的变量都改成long long。不要只在“你觉得会大的地方”改,一律改掉更省心。如果涉及取模,模数也建议用long long接收。这个错误的隐蔽性在于,小数据不会触发,你只有被评测系统教育一次才会长记性。
4.2 循环复杂度失控:1e5 的数据千万别套 O(n^2)
现象:程序在小数据上运行很快,换成实际测试数据后卡得不出结果,或者直接运行超时。有些同学以为是电脑太慢,其实不是。
原因:你写了一个 O(n^2) 的双重循环,数据规模却高达 10 万。10 万的平方是 100 亿,任何普通程序都跑不完。
解决:写循环嵌套之前先估算一下最大复杂度。预选赛的时限通常在一秒到三秒,一个普通 for 循环每秒大概能跑 1 亿次简单操作。如果你算出来总操作次数超过 1 亿,就要立刻考虑优化:能否用前缀和替代求和循环,能否用二分替代线性查找,能否用排序加双指针替代双重循环。把复杂度压到 O(nlogn) 是预选赛的核心能力。
4.3 cin 和 scanf 混用:同步开关背后的一个隐藏状态
现象:程序里同时用了cin和scanf读输入,结果发现有时候读到的值是错的,或者程序运行变慢到超时。
原因:cin和scanf使用不同的缓冲机制,混用时可能因为内部同步状态不一致导致读入顺序错乱,或者反复刷新缓冲区导致性能下降。
解决:要么全用cin,要么全用scanf,不要混。如果用cin,在main开头加上两行,按我的习惯是:
ios::sync_with_stdio(false); cin.tie(nullptr);这两行的作用是取消cin与stdio的同步,并解除cin与cout的绑定,能让输入输出速度明显提升。如果你决定用scanf,那就不要再用cin读任何东西。这个坑非常隐蔽,因为它不是每次都会出错,偶尔“玄学”地工作,偶尔又崩溃。
4.4 下标从 0 还是从 1 开始:一差之差让整道题结果偏移
现象:程序能运行,但输出结果从某一行开始整体偏移了一位。比如题目要求输出第 1 到第 n 项,你的程序输出了第 0 到第 n-1 项。
原因:你写循环时下标习惯从 0 开始,但题目里数组和序列是从 1 开始编号的。如果你的代码里数据结构的下标和题目的编号没有对齐,后续所有操作都会偏移一位。
解决:拿到题目先看数据描述的起始编号,如果题目说第 1 个元素,你最好在代码里也用 1-based。写vector时直接初始化大小为 n+1,下标从 1 开始用。千万别中途混合使用两种下标习惯,那会让代码变成一锅粥。
4.5 输出格式:行尾空格、最后一行换行这两个扣分点
现象:程序逻辑完全正确,但评测结果提示格式错误。
原因:多输出了空格,或者少输出了最后的换行。有些判题系统对行尾空格敏感,有些则只判断换行,但你没有必要去赌。
解决:输出每个元素时,判断当前是否最后一个,如果不是,才输出空格。循环结束后统一输出一个换行。把“最后一项单独处理”这个模版背下来,能覆盖绝大多数输出格式要求。
注意:预选赛的评测环境可能比本地更严格,任何格式错误都有可能导致非零分被扣除,提交前用样例数据逐字对比输出是一条保命线。
5. 考场时间里最该守住的一条线:时间分配与交卷前检查清单
考试拼的不只是你会不会,还有你在有限时间内的稳定性。预选赛通常两小时,四到六道题,很多同学的时间分配是“第一题磨半小时,最后一题只剩五分钟”,结果白白把送分题拖到来不及。我给你的建议是把时间切成三段。
5.1 两小时考试的时间分配表
| 时间段 | 动作 | 说明 |
|---|---|---|
| 开考后 10 分钟 | 通读全部题目 | 只读题,不写代码,用笔圈出每道题的数据范围 |
| 接下来 50 分钟 | 从前往后做模拟和数学题 | 先把稳定拿分的题做完做完写稳 |
| 再留 40 分钟 | 做数组字符串题 | 如果卡住超过 15 分钟,先跳过做下一个 |
| 最后 10 分钟 | 统一检查提交 | 不写新逻辑,只检查输出和格式 |
这个方案的核心原则是“先拿分,再冲分”。模拟和数学题是你最有把握的,先把它们彻底拿稳。数组字符串题如果超过 15 分钟没有进展,果断跳过,因为后面可能还有更值得的分数。
5.2 提交前花三分钟做的五个检查
交卷前五分钟,不要再去写新题,就做五件事:
第一,每道题重新用自己的样例跑一遍,确认输出和题目给的结果完全一致,包括空格和换行。第二,检查文件读写的适用范围:预选赛有的题目直接标准输入输出,有的要求指定文件名,千万别把freopen的路径写错。第三,代码里如果有调试用的cerr输出,全部删掉或注释掉。第四,确认提交的源代码是最终版本,不要出现编译器缓存了旧代码的情况。第五,把每个输出语句的后半段再读一遍,特别是if (j != i)这种行尾空格判断。
考场上最后一个习惯,是我自己练习时强迫自己不断养成的:不急着提交,先把刚写的代码从头到尾读一遍,用眼睛执行一次,看中间的变量推进是否和你脑中的逻辑一致。很多时候眼睛能发现手写的漏洞。
预选赛不是终点,它是给你一个“我的基本功到底过关没有”的答案。如果这次没过,把做错的题整理成错题本,按题型归类,下一次你会发现很多看起来不一样的题目,其实就是同一个套路换了一层外衣。希望这五章的内容能帮你把这份卷子的套路看清楚,也希望你在真正踏上考场的那一刻,心里装的不是紧张,而是一张清晰的地图。希望帮到你。
本文还有配套的精品资源,点击获取