news 2026/9/23 20:55:01

海淀区信息学竞赛预选赛真题详解:语法、程序阅读与算法建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
海淀区信息学竞赛预选赛真题详解:语法、程序阅读与算法建模

简介:2024年海淀区中小学生信息学竞赛校级预选赛试题,面向海淀区中小学生的信息学与编程基础选拔,可用于赛前模拟、知识自测与教师命题参考。试题含编程基础知识单选与程序阅读单选两类题型,覆盖变量命名、赋值语句、进制转换、表达式运算、函数与递归、循环控制、数组定义等核心考点;程序阅读部分要求根据给定代码推断输出结果,重点考察逻辑思维与代码调试能力。压缩包内含1个PDF文档,仅422KB,方便打印分发,适合课堂自测或居家练习。目前已有574人学习浏览,便于快速熟悉海淀区校级预选题型与难度。从预览可见,题目不仅考基础概念,还融入排序最少比较次数、分组方案计数等综合应用;程序阅读题涵盖因数计数、最大公约数与最小公倍数、回文数判断、素数筛选等经典算法,能帮助读者熟悉竞赛常见代码套路,提升读题与解题效率。

1. 海淀区这套校级预选赛,到底在筛什么

海淀区中小学生信息学竞赛的校级预选赛,往年最容易被低估。这份 1103 版本的 PDF 试题没有让考生直接写完整程序,而是把单选、程序阅读和问题阅读混在一张卷子里,恰好踩中了从语法入门到算法建模的过渡地带。对教练来说,它能判断一个学生是背了变量名规则,还是真的能在纸面上追踪循环和递归;对学生来说,这是一份极好的“体检报告”。下面按题型逐段拆解每一类题背后的考察意图、手算方法,并给出可复现的验证代码,适合准备海淀区复赛上机、以及想用原题做校内选拔的老师参考。

2. 基础选择:变量、二进制、表达式、循环的常见丢分点

2.1 变量名与赋值语句:C++ 语法的边界判断

校级预选赛的第一道分水岭,是变量名和赋值语句的合法性判断。合法变量名必须以字母或下划线开头,后面只能跟字母、数字、下划线,不能与 C++ 关键字冲突。int 2a;double a-b;char void;都是典型错误项。这里的坑往往不是“不知道规则”,而是看到熟悉的单词就放松警惕,比如float不是变量名,main虽然可以合法但容易误导。

赋值语句的考察更偏爱“边界写法”。char c = "a";是错误的,因为双引号表示字符串,不能直接赋给字符型变量;char c = 'a';才是正确写法。还有一类经典错误是混淆赋值号=和相等运算符==,在选择题里经常伪装成“表达式a+b的运算结果”这类说法。可以用下面这个程序快速验证数组初始化和字符赋值:

#include <bits/stdc++.h> using namespace std; int main() { // 合法的变量名:字母或下划线开头 int _ok = 1, ok2 = 2; // int 2ok = 3; // 非法:数字开头 // char class = 'a'; // 非法:关键字 char c1 = 'a'; // 正确:单引号包单个字符 // char c2 = "a"; // 错误:双引号是字符串 int a[3][2] = {2, 3, 4, 5, 6, 7}; cout << a[1][1] << endl; // 输出 5,即第二行第二列 return 0; }

代码里注释已经把变量命名和赋值两类坑标出来了。a[3][2]按行优先存储,初始化列表顺序依次填满第一行、第二行、第三行,所以a[1][1]对应第五个元素。这种题目不要求跑程序,但能在纸上画出一个 3 行 2 列的表格,答案就一目了然。

2.2 二进制转十进制与表达式优先级:算得快不如算得稳

二进制转十进制是竞赛入门必考。方法是从低位到高位按权展开,例如1011等于1*8 + 0*4 + 1*2 + 1*1 = 11。许多学生习惯从高位开始算,遇到1001这种对称数字容易漏位。更稳妥的做法是列一张权值表:

二进制位1011
权值8421
贡献8021

结果就是 8+2+1=11。原题里如果给出的是五位或六位二进制,只要把表往后扩展一倍即可。另一个常考概念是字符型变量能否参与算术运算,答案是可以。字符在表达式里会被提升为 ASCII 码,例如'A' + 1的结果是 66。可以用简短代码验证:

#include <bits/stdc++.h> using namespace std; int main() { cout << (7 / 2) << " " << (7 % 2) << endl; // 3 1 cout << ('A' + 1) << endl; // 66 return 0; }

注意7 / 2在整数除法下结果是 3,不是 3.5;%取余得到 1。表达式优先级从高到低大致是算术、关系、逻辑、赋值,赋值表达式本身也有值,比如a = b = 3会先给b赋 3,再把 3 赋给a。这类选择题真正的考点不是“会不会算”,而是“能不能在紧张状态下不踩优先级和结合性的坑”。

2.3 循环、数组和函数:概念题里的文字陷阱

循环语句的考察重点在forwhile的使用边界。for语句可以实现确定次数的循环,while同样可以,原题中“while 专用来实现不确定次数的循环”是错误的。break的作用是跳出当前循环,continue是跳过本次循环继续下一次,二者在被嵌套循环包裹时尤其容易混淆。

函数相关的概念题也有一个经典说法:C++ 中每个程序有且只有一个主函数,主函数是程序入口;函数支持嵌套调用,但不支持嵌套定义;递归函数是函数自己调用自己。题目里如果出现“函数不支持嵌套”这种表达,要结合上下文判断,通常指的是嵌套定义而不是嵌套调用。数组题则重点看下标从 0 开始,int a[3][2]只有a[0][0]a[2][1],访问a[2][2]已经越界,但很多学生下意识认为第二维下标也可以是 2。建议平时做题时把所有数组下标都写成从 0 开始,并且养成“先看边界再看值”的习惯。

3. 程序阅读:手算与机算对照,六段代码的拆解

3.1 因数计数、GCD 与 LCM:别只盯着循环范围

程序阅读第一题输入n,循环for(int i=1; i<n; i++)统计能整除ni的个数。注意循环从 1 到n-1,不包含n本身。比如输入 12,i=1,2,3,4,6都能整除,输出 5。如果输入 6,输出 3。这里的易错点是漏掉 1,或者把i<n看成i<=n

第三题是典型的求最小公倍数:c = min(a, b)后从大到小找最大公约数d,最后输出a*b/d。这个方法本身没有问题,但a*b可能溢出,竞赛中更稳的写法是a / d * b。可结合下面的表格理解三题的程序结构:

题号核心变量循环边界输出含义
1n, cnti=1..n-1n 的除本身外因数个数
2n, m, cnti=1..n,cnt==6 提前 break同时整除 n 和 m 的因数个数
3a, b, c, di=c..1,找到即 breaka 和 b 的最小公倍数

第二题里cnt==6就提前结束,这是第一个坑。如果手算时不停下来,会继续数到很多因数。正确的追踪方式是同时列三个变量:当前i、条件是否成立、当前cnt。这样即使题目改成cnt==10也能快速迁移。

3.2 回文数与素数筛:从函数到前缀和

第四题实现了一个回文数判断函数f(x),主程序统计1..n中回文数的个数。手算时可以从 1 开始枚举:1 到 9 全是回文数,11 到 99 中的回文数有 11、22、……、99。如果输入 11,输出是 10。这个题目的价值在于“函数返回值参与计数”,而不是直接输出判断结果。

第五题是埃氏筛的变种。先用a[i*j]=1标记合数,再做a[i]=a[i-1]+(1-a[i])的前缀和,最终a[n]表示n以内素数的个数。注意代码里先执行了a[1]=1,把 1 排除在素数之外。这个程序表面上是筛法,实际上考察的是“数组复用”:同一个数组先做标记,再做前缀和。可以把它压缩成验证代码:

#include <bits/stdc++.h> using namespace std; int n, a[10010]; int main() { cin >> n; a[1] = 1; for (int i = 2; i <= 10000; i++) { if (a[i] != 0) continue; for (int j = 2; j <= 10000 / i; j++) a[i * j] = 1; } for (int i = 1; i <= n; i++) a[i] = a[i - 1] + (1 - a[i]); cout << a[n]; return 0; }

外循环只筛到10000/i,避免重复标记;内层j从 2 开始,所以i本身不会被标记。这个写法比直接j=i; j<=10000; j+=i少做大量无用遍历,但结果一致。需要留意的是,i*j可能超过数组范围,所以循环条件用j <= 10000 / i更安全。

3.3 素性判断与哥德巴赫猜想模拟:分支顺序决定结果

第六题先写了一个素数判断函数f(x),主程序的分支顺序很讲究:如果f(n)为真输出 1;否则如果n是偶数输出 2;否则如果n-2是素数输出 2;否则输出 3。这个逻辑本质上是验证“奇数能否拆成两个素数之和”。

手算时要注意顺序不能颠倒。比如输入 27,27 不是素数,也不是偶数,但 27-2=25 不是素数,所以输出 3。如果输入 25,25 不是素数,不是偶数,但 25-2=23 是素数,输出 2。这道题几乎不考复杂算法,考的是“你有没有把if/else if当成顺序执行”。很多学生看到f(n)为真就直接选 1,却没有意识到后续分支对非素数也做了判断。

第七题是递归求最大公约数,代码只有六行。看这类递归程序,不要展开所有调用层,应该先找递归出口if(b==0) return a;,再沿参数变化写一行链条。例如输入 12 和 8,调用链是f(12,8) -> f(8,4) -> f(4,0),结果 4。如果题目输入的两个数比较大,就观察a%b的变化,通常三五步内就能收敛。

3.4 死循环、continue 与因子计数:跟踪 i 的变化

第八题是一个没有显式退出条件的while(1),它在内部通过break结束。程序先执行i++,再判断i%7!=0,只有 7 的倍数才会进入后面的因子计数。因子计数统计的是2..i-1之间能整除i的个数,也就是i除了 1 和自身之外的因数个数。当这个个数等于 6 时,说明i总共有 8 个因数。

手算时可以列出 7 的倍数并记录因子个数:

i除 1 和自身外的因数cnt是否 break
70
142,72
213,72
282,4,7,144
355,72
422,3,6,7,14,216

所以最终输出 42。这个表本身就是最好的追踪方法。验证代码可以直接复制到本地:

#include <bits/stdc++.h> using namespace std; int main() { int i = 1, ans = 0; while (1) { i++; if (i % 7 != 0) continue; int cnt = 0; for (int j = 2; j < i; j++) if (i % j == 0) cnt++; if (cnt == 6) { ans = i; break; } } cout << ans; return 0; }

i++放在循环体开头,意味着第一次检查的是 2,而不是 1。continue会把非 7 的倍数直接跳过,所以表里只需要关注 7 的倍数。这里最隐蔽的坑是“因子计数个数等于 6”并不代表这个数只有 8 个因数,还要确认 1 和自身是否已经被自动包含,而代码中的cnt确实不包含 1 和i,所以cnt==6等价于总因数个数为 8。

4. 应用题:屏幕翻页、视频压缩、骰子与爬山,建模比写代码更重要

4.1 手机屏幕:位置编号与图标编号要分开算

手机屏幕题给出了应用排列、屏幕容量和启动顺序,但原始数据在试卷里被滤镜抹掉了。真正需要掌握的是一个通用映射:应用位置pos从 1 开始,所在屏幕编号等于(pos-1)/k+1,屏幕内位置等于(pos-1)%k+1。每次启动一个应用,先计算它当前所在屏幕,滚动操作次数就是该屏幕编号与 1 的差值再加启动一次。

启动后还要把该应用的位置与前一位置交换。如果它已经在位置 1,则不交换。这里最容易搞混的是“应用编号”和“位置编号”,位置编号会随着交换变化,应用编号不变。建议维护两个数组:loc[app]表示应用当前在哪个位置,who[pos]表示位置pos上是哪个应用。交换时同步更新两个数组。代码框架如下:

#include <bits/stdc++.h> using namespace std; const int MAXN = 1005; int loc[MAXN], who[MAXN]; int main() { int n, m, k; cin >> n >> m >> k; for (int i = 1; i <= n; i++) { int app; cin >> app; who[i] = app; loc[app] = i; } long long total = 0; for (int t = 1; t <= m; t++) { int app; cin >> app; int p = loc[app]; int screen = (p - 1) / k + 1; total += screen; // 滚动到目标屏幕 + 双击启动 if (p > 1) { int prevApp = who[p - 1]; swap(who[p], who[p - 1]); swap(loc[app], loc[prevApp]); } } cout << total; return 0; }

total累加的是每次启动所需操作数。这里的简化是假设回到第一屏不需要额外次数,因为题目明确说菜单自动返回第一屏。如果要扩展成“每次从当前屏幕开始”,只需要维护当前屏号,再计算两屏之间的最短滚动距离,但原题语境并不需要。

4.2 多服务器视频压缩:贪心调度与队头等待

视频压缩题给了多个服务器,每个服务器同一时刻只能压一个视频,视频按上传时间排队,有服务器空闲就立即开始。这种模型在操作系统调度里叫“多队列最早可用时间”,实现时不需要真的维护队列,只需要记录每个服务器的“下次空闲时刻”。

finish_time[i]为第i台服务器的当前空闲时刻。新任务上传时间为t,最早空闲的服务器是finish_time最小的那个,开始时间等于max(t, finish_time[pos]),完成时间等于开始时间加上视频时长,然后更新该服务器。下面的代码可以处理题目中“除第 1 个和第 n 个视频外,其余视频长度相同”的特判:

#include <bits/stdc++.h> using namespace std; const int MAXM = 105; long long finish_time[MAXM]; int main() { int m, n; cin >> m >> n; for (int i = 1; i <= n; i++) { long long t; cin >> t; int len = 1; if (i == 1 || i == n) len = 2; // 按试卷模型修改 int pos = 1; for (int j = 2; j <= m; j++) if (finish_time[j] < finish_time[pos]) pos = j; long long start = max(t, finish_time[pos]); finish_time[pos] = start + len; cout << "video " << i << " finish at " << finish_time[pos] << endl; } return 0; }

finish_time初始为 0,所以第一个视频上传后立刻开始。max(t, finish_time[pos])处理两种情况:服务器已经空闲但视频还没上传,或者视频已经上传但服务器还在忙。多服务器一起工作时,这个模型天然支持并行,不需要额外判断“是否有空闲服务器”。

4.3 掷骰子与爬山:把组合枚举转成判定

骰子题的核心是“某个骰子上的所有数字都可能出现”。假设有n个骰子,第i个骰子的面数为face[i],所有骰子同时掷出后总和为S。要判断第p个骰子的某个面值x是否能出现,只需要看其他骰子的最小可能和与最大可能和是否覆盖S-x。其它骰子的最小和是每个骰子的面值 1 相加,最大和是每个骰子的面值上限相加:

#include <bits/stdc++.h> using namespace std; const int MAXN = 105; int face[MAXN]; int main() { int n, S; cin >> n >> S; int sumMin = 0, sumMax = 0; for (int i = 1; i <= n; i++) { cin >> face[i]; sumMin += 1; sumMax += face[i]; } for (int p = 1; p <= n; p++) { int otherMin = sumMin - 1; int otherMax = sumMax - face[p]; bool ok = true; for (int x = 1; x <= face[p]; x++) { if (otherMin > S - x || otherMax < S - x) ok = false; } if (ok) cout << "dice " << p << " all faces possible" << endl; } return 0; }

参数说明:otherMin是去掉第p个骰子后其余骰子的最小总和,otherMax是最大总和。只要S-x落在这个闭区间内,就存在一种组合。这个判断方法把指数级枚举压缩成一次区间判定,是校赛题里常见的“思维转化”。

小猴爬山题则是典型的一维随机游走。第 1 天和第n天海拔都是 0,相邻两天高度差不超过 1,问最高可能海拔。从极限角度看,想爬到高度H,至少需要H天向上、H天向下,所以H <= (n-1)/2向下取整。比如n=5时最高为 2,路径可以是 0,1,2,1,0;n=4时最高为 1,路径可以是 0,1,1,0。如果题目额外限制某些天必须经过某个高度,就在这个公式基础上用区间交判断,不能直接套结论。

5. 用这套真题组织一次校内选拔:判分、讲评与赛前冲刺

5.1 限时与判分策略

校级预选赛的定位是筛选,不是竞赛,建议把时长控制在 60 到 90 分钟。单选题占三十分钟,程序阅读占三十分钟,综合题最后做。判分时不要只看答案,程序阅读题可以要求学生写出关键变量变化表,例如i=28, cnt=4这种过程记录,能有效防止蒙答案。PDF 原题可以直接打印,也可以用问卷星做成在线版,自动统计每道题的错误率。

5.2 用 G++ 复现答案做交叉验证

上文的每段代码都可以单独保存成check.cpp,用g++编译后通过管道输入测试数据:

g++ check.cpp -o check echo "12" | ./check

例如复现程序阅读第一题,输入 12 应输出 5;复现第三题,输入4 6应输出 12;复现第四题,输入 11 应输出 10。老师可以在课前跑一遍,把输出和手算结果对照,快速排查自己是否读错循环边界。注意每个题单独一个cpp文件,变量名重复不影响编译。

5.3 讲评时最值得强调的三个习惯

第一是变量改名。读程序时把cnt改写成“因数个数”,把ans改写成“答案”,能减少一半低级失误。第二是画跟踪表。遇到whilebreak组合时,列三列:当前循环变量、条件结果、计数变量,一行一行更新,比在脑子里空转可靠得多。第三是“先算范围再猜答案”。综合题里的数字往往故意给得很大,但屏幕题的核心是除法取整,爬山题的核心是公式,视频压缩题的核心是最小堆,复数数据反而是干扰项。把这套题拆完以后,可以按照错误率最高的三道题重新组一张十分钟小卷,下一轮训练直接用它做课前测,效果比整套重做更明显。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 20:50:54

深度强化学习重构时间序列预测:DQN框架与实战解析

简介&#xff1a;在时间序列预测任务中引入深度强化学习&#xff0c;是近年来的研究热点之一。该zip包以DRL为工具解决序列预测问题&#xff0c;面向具备Python和机器学习基础、希望进阶强化学习的开发者。项目围绕DQN等模型展开&#xff0c;将预测转化为智能体在环境中的决策过…

作者头像 李华
网站建设 2026/9/23 20:38:55

基于计算机视觉的道路坑洼检测:多种算法模型对比与Python实战

简介&#xff1a;这份资源是面向计算机相关专业学生与项目实战学习者的道路坑洼检测课程设计资料&#xff0c;基于计算机视觉方法实现路面病害识别&#xff0c;并横向对比AlexNet、LeNet-5、LeNet-5 2.0等多种算法模型的检测效果&#xff0c;适合作为毕设、课设、期末大作业或算…

作者头像 李华
网站建设 2026/9/23 20:38:52

饥荒机器人全攻略:解锁、齿轮升级与成神养成路线

1. 玩机器人之前&#xff0c;先搞清楚这几点饥荒里的机器人&#xff08;WX-78&#xff09;是个特别容易让人又爱又恨的角色。爱的是他后期属性爆炸&#xff0c;恨的是他前期脆得跟纸一样&#xff0c;而且一碰雨水就掉血。很多新手第一次选到他&#xff0c;活不过三天就直接放弃…

作者头像 李华
网站建设 2026/9/23 20:38:50

AM非相干解调实战:从Matlab仿真到FPGA定点部署

简介&#xff1a;本资源是一套面向通信工程专业学生及初学者的AM调制与非相干解调MATLAB仿真教学包&#xff0c;聚焦模拟通信系统核心原理实践&#xff0c;解决理论抽象、波形难观测、解调同步机制理解困难等学习痛点。压缩包含2个关键M文件&#xff1a;sim_AM_modem_ex1.m实现…

作者头像 李华
网站建设 2026/9/23 20:37:04

气象站异常检测:基于图信号处理与时间序列分析的Python实现

简介&#xff1a;一套面向计算机、信号处理方向课程设计的气象站异常检测系统源码包&#xff0c;基于Python实现&#xff0c;通过图模型对气象站空间关系建模&#xff0c;结合纬度差与时间序列历史差异识别异常&#xff0c;并融合两类结果提升准确率。压缩包共5个文件&#xff…

作者头像 李华