ARTICLE DETAIL

资讯详情

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

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

海淀区信息学竞赛预选赛真题详解:语法、程序阅读与算法建模 简介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;才是正确写法。还有一类经典错误是混淆赋值号和相等运算符在选择题里经常伪装成“表达式ab的运算结果”这类说法。可以用下面这个程序快速验证数组初始化和字符赋值#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结果就是 82111。原题里如果给出的是五位或六位二进制只要把表往后扩展一倍即可。另一个常考概念是字符型变量能否参与算术运算答案是可以。字符在表达式里会被提升为 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 循环、数组和函数概念题里的文字陷阱循环语句的考察重点在for和while的使用边界。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 i1; in; i)统计能整除n的i的个数。注意循环从 1 到n-1不包含n本身。比如输入 12i1,2,3,4,6都能整除输出 5。如果输入 6输出 3。这里的易错点是漏掉 1或者把in看成in。第三题是典型的求最小公倍数c min(a, b)后从大到小找最大公约数d最后输出a*b/d。这个方法本身没有问题但a*b可能溢出竞赛中更稳的写法是a / d * b。可结合下面的表格理解三题的程序结构题号核心变量循环边界输出含义1n, cnti1..n-1n 的除本身外因数个数2n, m, cnti1..ncnt6 提前 break同时整除 n 和 m 的因数个数3a, b, c, dic..1找到即 breaka 和 b 的最小公倍数第二题里cnt6就提前结束这是第一个坑。如果手算时不停下来会继续数到很多因数。正确的追踪方式是同时列三个变量当前i、条件是否成立、当前cnt。这样即使题目改成cnt10也能快速迁移。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本身不会被标记。这个写法比直接ji; j10000; ji少做大量无用遍历但结果一致。需要留意的是i*j可能超过数组范围所以循环条件用j 10000 / i更安全。3.3 素性判断与哥德巴赫猜想模拟分支顺序决定结果第六题先写了一个素数判断函数f(x)主程序的分支顺序很讲究如果f(n)为真输出 1否则如果n是偶数输出 2否则如果n-2是素数输出 2否则输出 3。这个逻辑本质上是验证“奇数能否拆成两个素数之和”。手算时要注意顺序不能颠倒。比如输入 2727 不是素数也不是偶数但 27-225 不是素数所以输出 3。如果输入 2525 不是素数不是偶数但 25-223 是素数输出 2。这道题几乎不考复杂算法考的是“你有没有把if/else if当成顺序执行”。很多学生看到f(n)为真就直接选 1却没有意识到后续分支对非素数也做了判断。第七题是递归求最大公约数代码只有六行。看这类递归程序不要展开所有调用层应该先找递归出口if(b0) 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是否 break7无0否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所以cnt6等价于总因数个数为 8。4. 应用题屏幕翻页、视频压缩、骰子与爬山建模比写代码更重要4.1 手机屏幕位置编号与图标编号要分开算手机屏幕题给出了应用排列、屏幕容量和启动顺序但原始数据在试卷里被滤镜抹掉了。真正需要掌握的是一个通用映射应用位置pos从 1 开始所在屏幕编号等于(pos-1)/k1屏幕内位置等于(pos-1)%k1。每次启动一个应用先计算它当前所在屏幕滚动操作次数就是该屏幕编号与 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向下取整。比如n5时最高为 2路径可以是 0,1,2,1,0n4时最高为 1路径可以是 0,1,1,0。如果题目额外限制某些天必须经过某个高度就在这个公式基础上用区间交判断不能直接套结论。5. 用这套真题组织一次校内选拔判分、讲评与赛前冲刺5.1 限时与判分策略校级预选赛的定位是筛选不是竞赛建议把时长控制在 60 到 90 分钟。单选题占三十分钟程序阅读占三十分钟综合题最后做。判分时不要只看答案程序阅读题可以要求学生写出关键变量变化表例如i28, cnt4这种过程记录能有效防止蒙答案。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改写成“答案”能减少一半低级失误。第二是画跟踪表。遇到while和break组合时列三列当前循环变量、条件结果、计数变量一行一行更新比在脑子里空转可靠得多。第三是“先算范围再猜答案”。综合题里的数字往往故意给得很大但屏幕题的核心是除法取整爬山题的核心是公式视频压缩题的核心是最小堆复数数据反而是干扰项。把这套题拆完以后可以按照错误率最高的三道题重新组一张十分钟小卷下一轮训练直接用它做课前测效果比整套重做更明显。本文还有配套的精品资源点击获取
返回列表