简介:面向海淀区信息学竞赛校级预选阶段的这份PDF试卷,适合中小学生选手及指导教师用于考前自测、知识点排查与课堂训练。内容由编程基础知识单选题和程序阅读单选题组成,覆盖变量命名、赋值语句、数据类型、进制转换、表达式运算、逻辑判断、函数定义与调用、循环及数组等考点,还涉及合法变量名判断、二进制与十进制互转、字符型变量参与运算、for与while循环特性以及break、continue的作用;程序阅读题则包含约数计数、最大公约数与最小公倍数、回文数判断、素数筛法等典型算法情境,重点检验代码阅读与逻辑推理能力。包内仅含1个PDF文件,约422KB,结构紧凑,可直接打印成卷或按题块拆分练习。已有588人学习,便于读者快速了解预选赛题型难度、定位薄弱环节,并据此安排基础语法复习和程序阅读专项训练。
1. 校级预选赛不是"编程比赛",是三块送分题的组合
海淀区信息学竞赛校级预选赛这份卷子,第一眼看像一张 C++ 语法测验,实际它把分数切成三块:12 道编程基础知识单选题、8 道程序阅读单选题、4 道问题阅读单选题。三块的答题逻辑完全不同——第一类靠背和辨析,第二类靠"人肉编译",第三类靠把长题干翻译成可执行的模型。对刚学完循环和数组的同学来说,难度不在算法本身,而在细节有没有盯住。想达到晋级区级初赛的分数,单选基础部分是最稳的提分池,程序阅读和问题阅读是拉分点。下面按这三块拆开,把每类题的答题方法、高频坑和临场判断讲透。
2. 编程基础知识:12 道语法判定题怎么提到接近满分
这一块是整张卷子的送分区,考的全是 C++ 入门阶段必须拿死的东西:变量命名、赋值语句、进制转换、表达式求值、函数特性、循环语义、二维数组下标。出题人设计的干扰项都长得"像对的",尤其赋值和进制两类,稍微手快就掉坑。要把这块做稳,靠的不是临场推理,而是把规则提前背成条件反射。下面分三组讲,每组给规则、给典型题、给判断口诀。
2.1 变量命名与赋值语句:两条铁律管住一大半题
合法变量名(标识符)的判定,考试只考四条:第一,首字符必须是字母或下划线,数字不能打头;第二,后续字符只能是字母、数字、下划线,不能出现-、.、+、$这类符号;第三,不能是 C++ 关键字(int、for、while、return、double、char这些);第四,区分大小写,Sum和sum是两个变量。选项里最常出现的干扰项是2a、a-b、a.b、for、int这几种,见到就打叉。还有一类容易犹豫的:main——它不是 C++ 关键字,但它是程序入口函数名,绝大多数教材把它排除在变量名之外,考试选"合法变量名"时不要选它,把main当保留名处理最稳。
赋值语句的判定,核心只有一句:等号左边必须是一个可以修改的左值。能作为左值的是变量、数组元素、解引用指针;不能作为左值的是常量、字面量、表达式。于是下面几种写法一眼就能判错:
5 = a;:左边是常量,错。a + 1 = 5;:左边是表达式,错。"a" = 5;:左边是字符串字面量,错。'a' = 5;:左边是字符常量,错。a = b = 5;:合法,赋值表达式本身有值,从右往左结合,先把 5 赋给 b,再把 b 的值赋给 a。
给字符变量赋字符'0',本质是赋 ASCII 码 48。所以c = '0';和c = 48;都对,而c = 0;赋的是空字符'\0',c = '\0';也是空字符。这道题的干扰项就是拿 0 和 48 混。记住口诀:字符'0'是数字 48,空字符'\0'才是 0,两个都写成 0 的选项必错。
提示:赋值题先看等号左边,再看等号右边。左边不是变量,直接判错,不用管右边写得多漂亮。
2.2 进制转换与表达式求值:按位权展开,别用计算器思维
二进制转十进制,考的是位权展开:从右往左第 k 位(k 从 0 开始)的权是 2 的 k 次方,把每一位乘权再求和。比如一个 8 位二进制数,最高位权是 128,最低位权是 1。题目一般给 4 到 8 位,手算完全来得及,但要在草稿纸上把权标出来再乘,不要凭感觉。常见的送分干扰项是把某一位的权记错,比如把从右数第三位的权当成 3 而不是 4,或者把二进制当成八进制处理。
表达式求值这块,考的是运算符优先级和类型语义。优先级从高到低记四层就够用:括号 > 单目(!、++、--、负号) > 算术(* / %高于+ -) > 关系(< <= > >=) > 逻辑(&&高于||) > 赋值。考试最爱挖的坑有三个:
第一,整除和取模。两个整数相除结果取整、向零截断,7 / 2得 3,-7 / 2得 -3;%的结果符号跟被除数一致,-7 % 2得 -1。只要有一个操作数是浮点数,除法才保留小数。
第二,&&和||的短路。a && b当 a 为假时不再算 b,a || b当 a 为真时不再算 b。题目如果混入自增自减,短路会改变结果,这是最容易翻车的地方。
第三,关系表达式和逻辑表达式的结果是 bool 值(0 或 1),不是操作数本身。5 > 3的值是 1,3 > 5的值是 0,别写成"真"和"假"两个字去选。
还有一道常考的组合题:把 N 个人分成三组,要求任意两组人数差不超过 1,问有多少种分法。这类题不要硬推公式,直接按"最少的一组"枚举。设三组人数从小到大为 a、a、a 或 a、a、a+1 两种形态,先算总数 N 除以 3 的商和余数,余数决定有几个组多 1 个人,再用排列组合处理三组是否区分。考试时用小的 N 代进去验证,比记公式可靠。
注意:看到
&&、||连着++、--的表达式,先在草稿上标出短路位置,再逐项算,别跳步。
2.3 函数、循环、二维数组:语法细节题的正确率靠"逐项排除"
函数特性题,五个说法里通常只有一个错。常考的点:每个程序有且只有一个主函数main,对;函数可以自己调用自己,只有递归函数才这样,说"所有函数都可以自己调用自己"就错;主函数是程序执行入口,对;递归函数是函数自己调用自己的典型代表,对;C++ 不支持函数嵌套定义,但支持嵌套调用,题目如果说"函数不支持嵌套",要看清它指的是定义还是调用——说"定义"是对的,说"调用"是错的。这类题用逐项排除,把明显违背教材原话的先划掉。
循环语句题,考 for 和 while 的等价性以及 break、continue 的语义。for 和 while 都能实现确定次数和不确定次数的循环,说"while 专用来实现不确定次数的循环"是错的,这是最常见的错误选项。break跳出整个循环,continue只跳过本次进入下一轮,两者不能混。还有一个细节:for(int i = 0; i < n; i++)里 i 的作用域只在循环内,循环外访问 i 会编译错误,这一点在程序阅读题里会反复出现。
二维数组题,纯粹考下标的展开顺序。int a[3][2] = {2, 3, 4, 5, 6, 7};是按行优先填充的:a[0][0]=2、a[0][1]=3、a[1][0]=4、a[1][1]=5、a[2][0]=6、a[2][1]=7。找值为 5 的元素,答案是a[1][1]。做这类题不要心算,直接在草稿上画一个 3 行 2 列的表格,把数字填进去再对选项,十秒钟的事,比在脑子里绕可靠得多。
3. 程序阅读:8 段代码的走读训练
程序阅读是整张卷子里最能拉开差距的部分,8 道题全是给一段 15 行以内的 C++ 代码,问输入某个数时输出是什么。这类题不考算法设计,考的是"你能不能像编译器一样把代码走一遍"。做题方法固定成三步:第一步看全局变量有没有初值(全局变量默认 0,局部变量不初始化是随机值,这是最高频的坑);第二步找循环边界,尤其是i < n还是i <= n、从 1 开始还是从 0 开始;第三步拿题目给的小输入手动模拟两三圈,找出规律再对选项。下面按三类典型程序拆。
3.1 计数类循环:起点和终点决定一切
最典型的一段是统计因数的代码:
#include <bits/stdc++.h> using namespace std; int n, cnt; // 全局变量,cnt 默认初值 0 int main() { cin >> n; for (int i = 1; i < n; i++) // 注意是 i < n,不含 n 本身 if (n % i == 0) cnt++; // 统计 [1, n-1] 内能整除 n 的数 cout << cnt; return 0; }这段代码统计的是 n 的真因数个数(不包括 n 本身)。判断的关键在循环条件i < n:如果写成i <= n,就把 n 自己也算进去了,答案会多 1。做这类题,先在草稿上写"i 从 1 到 n-1",再拿一个具体数比如 n=12 试:12 的真因数是 1、2、3、4、6,共 5 个,输出 5。用一个小例子验证边界,比盯着代码看有效。
再看一段统计公共因数的:
int n, cnt, m; int main() { cin >> n >> m; for (int i = 1; i <= n; i++) // 从 1 枚举到 n { if (m % i == 0 && n % i == 0) cnt++; // i 同时整除 m 和 n if (cnt == 6) break; // 数到 6 就提前退出 } cout << cnt; return 0; }这段统计的是 n 和 m 的公共因数个数,但有一个截断:数到 6 就 break,所以最终输出是min(公共因数个数, 6)。题目给的输入如果公共因数少于 6 个,输出实际个数;如果大于等于 6 个,一律输出 6。这类题最容易错的地方是没看见break,凭"公共因数"四个字就选了一个大数。走读代码时,把 break、continue、return 这三个提前退出的语句用笔圈出来,是硬习惯。
3.2 数论函数:gcd、回文、质数判定各有一套模板
求最大公约数的递归写法几乎每年都出现:
int x, y; int f(int a, int b) { if (b == 0) return a; // 递归出口:余数为 0 时,a 就是 gcd return f(b, a % b); // 辗转相除 } int main() { cin >> x >> y; cout << f(x, y); return 0; }这段是欧几里得算法,输出 x 和 y 的最大公约数。判断方法很简单:看到a % b和b == 0的组合,直接认成 gcd。如果题目问的是另一个变体——先求 gcd 再输出a * b / d,那输出就是最小公倍数。这两个模板要背到看见就能反应:gcd 用辗转相除,lcm 等于两数之积除以 gcd。
回文数判定也是固定套路:
int n, cnt; bool f(int x) { int s = 0, t = x; while (t != 0) { s = s * 10 + (t % 10); t /= 10; } // 反转数字 return (s == x); // 反转后相等即为回文 } int main() { cin >> n; for (int i = 1; i <= n; i++) if (f(i)) cnt++; // 统计 1 到 n 中的回文数个数 cout << cnt; return 0; }while循环里s = s * 10 + t % 10是标准的数位反转写法,循环结束时 s 是 x 的反转数,再和 x 比较。这段代码输出 1 到 n 之间的回文数个数。走读时注意t = x把原数备份了一份,如果直接改 x,后面就没法比较了,这是函数参数按值传递的典型用法。
3.3 筛法与前缀和:第二眼才看懂的代码
筛法那段代码是全卷最需要"读两遍"的:
int n, a[10010]; int main() { cin >> n; a[1] = 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; // 把 i 的倍数标记为合数 } for (int i = 1; i <= n; i++) a[i] = a[i - 1] + (1 - a[i]); // 前缀和:a[i] 变成 1 到 i 的素数个数 cout << a[n]; return 0; }第一段是埃氏筛:a[i] = 0表示素数,a[i] = 1表示合数,外层枚举 i,内层把 i 的倍数全部标记。第二段是前缀和,把"是否为素数"累加成"1 到 i 有几个素数"。最终输出 n 以内的素数个数。走读这类代码,关键是认出两层结构:前面是标记,后面是统计。看到a[i] = a[i - 1] + (1 - a[i])这种把数组自己当累加器的写法,就是前缀和,不用慌。
还有一段判断素数的函数配合条件分支:
bool f(int x) { for (int i = 2; i * i <= x; i++) if (x % i == 0) return 0; // 找到因子,不是素数 return 1; } int main() { cin >> n; if (f(n)) cout << 1; // n 本身是素数 else if (n % 2 == 0) cout << 2; // n 是偶数,可拆成两个素数之和 else if (f(n - 2)) cout << 2; // n-2 是素数,n = 2 + (n-2) else cout << 3; // 其余情况拆成三个素数 }这段的输出只可能是 1、2、3 三种,背后是哥德巴赫猜想的验证思路:偶数拆两个素数,奇数如果减 2 是素数就拆成 2 加一个素数,否则拆三个。题目问输出,只要按分支顺序对号入座,n 是素数输出 1,n 是偶数输出 2,n 减 2 是素数输出 2,剩下输出 3。注意i * i <= x是素数判定的标准写法,比i <= sqrt(x)快且不用浮点。
提示:程序阅读题不要一行一行翻译,先扫一遍有没有全局变量、有没有 break/continue/return、循环从几到几,这三个信息定了,答案基本就定了。
4. 问题阅读:四道长题干怎么翻译成模型
问题阅读是这份卷子里最像"真正的信息学题"的部分,四道题都是长题干加几个选项,问"下列说法正确/错误的是"。题干动辄一两百字,但真正用来解题的信息往往只有三五行。做这类题的方法不是通读,而是先看问法,再回题干找约束:问"多少次操作"就是模拟题,问"哪个说法错误"就是逐个选项验证,问"最高可能值"就是极值推理。下面按题型拆。
4.1 模拟类:把规则逐条写成状态变化
手机屏幕滑动那道题是标准的模拟。题干给的信息要拆成三块:屏幕大小 k(每屏 k 个图标)、初始排列(第 i 号应用在哪个位置)、启动序列(按顺序启动哪些应用)。操作数的计算规则是:从第 1 屏滚动到第 p 屏需要 p-1 次滚动,再加 1 次点击启动,共 p 次;启动后菜单回到第 1 屏,并且被启动的应用与它前一个位置的应用交换图标。
这类题最容易错在两个地方。第一,混淆"应用编号"和"位置编号"。题干专门提醒了这是两个不同的变量,应用编号是身份,位置编号是它在菜单里的坐标,交换的是图标位置,不是应用编号。第二,忘了"启动后回到第一屏"这个重置动作,导致把上一次的屏幕位置累加到下一次。答题时在草稿上画一张表,列三栏:当前应用编号、它当前所在位置、本次操作数,每启动一个就更新一次交换,照着表格加总,不要心算。
4.2 调度类:并行服务器的完成时间要画时间轴
视频压缩那道题考的是多服务器并行调度。规则要抓四条:服务器按视频上传时间先后处理;每台服务器每 S 秒压缩 1 分钟时长的视频;同一时刻一台服务器只处理一个视频,多个服务器可以并行;同一个视频不能拆分到多台服务器。题目给 N 台服务器、M 个视频的上传时刻和时长,问哪个视频的完成时间说法错误。
解这类题的标准动作是画时间轴。先按上传时间把视频排好,维护每台服务器的"空闲时刻",每个视频到来时,选空闲时刻最早的那台服务器开始处理,完成时刻等于开始时刻加上处理时长(注意单位换算:每 S 秒处理 1 分钟,处理 t 分钟需要 t×S 秒)。四个选项逐个代进去核对,哪个对不上就是答案。单位换算是最大的坑,题干里"秒"和"分钟"混着出现,先把所有时间统一成秒再算,能避开一半错误。
4.3 逻辑推理类:极值问题用"两端夹逼"
掷骰子和小猴爬山这两道,表面是应用题,内核是极值推理。掷骰子题:N 个骰子面数各不相同,同时掷出后数字之和为 S,判断哪些骰子的哪些数字可能出现。做法是对每个骰子单独算它的取值范围:其他骰子都取最小值时,这个骰子能取到的最大值是 S 减去其他骰子最小值之和;其他骰子都取最大值时,这个骰子能取到的最小值是 S 减去其他骰子最大值之和。落在这个区间内的数字才可能出现,区间外的直接排除。四个选项逐个套这个上下界,就能找出错误项或正确项。
小猴爬山题:N 天的海拔记录,第 1 天和第 N 天固定,任意连续两天的海拔差不超过 d,问可能到过的最高海拔。这是典型的差分约束变形,用"从左往右推上界、从右往左推上界"两遍夹逼:从左往右,第 i 天的上界是第 1 天加 (i-1)×d;从右往左,第 i 天的上界是第 N 天加 (N-i)×d;两个上界取较小值,就是第 i 天可能达到的最高海拔,再对所有天取最大。答题时不需要写代码,拿这个思路对四个选项,哪个超过上界就排除。
5. 易错点:预选赛里最容易失分的五类题
这一章单独拎出来,是因为下面五类错误在历年卷子里反复出现,而且都不是"不会",是"看漏了"。每一条按"错在哪、为什么错、怎么答对"写清楚,考前过一遍比多刷十道题管用。
5.1 循环边界看反:<和<=差一个答案
错在哪:统计因数、统计区间个数时,把i < n看成i <= n,输出多 1 或少 1。为什么错:C++ 的循环条件写在括号里,视觉上很容易被if那一行盖过去,尤其代码缩进乱的时候。怎么答对:走读代码时先用笔把循环头圈出来,在草稿上写一行"i 的取值是 [1, n-1]",再开始模拟。凡是答案选项里有两个只差 1 的,先怀疑边界,回头核对循环条件。
5.2 全局变量没看见:cnt 没初始化为什么不是垃圾值
错在哪:看到int cnt;就以为初值是随机的,不敢算。为什么错:全局变量和静态变量在程序启动时自动初始化为 0,只有函数内部的局部变量才是不确定的。这段代码里n、cnt都定义在main外面,初值就是 0。怎么答对:扫一眼变量定义的位置,在main外面的是全局,在main里面的是局部。全局的计数变量默认从 0 开始,直接按 0 累加;局部的如果没赋值,答案就没法确定,题目一般不会这么出。
5.3 break 和 continue 的位置:提前退出会截断统计
错在哪:统计公共因数时忽略if (cnt == 6) break;,按"全部公共因数"去选答案。为什么错:break 在循环体里,每轮都可能触发,统计到 6 就停了,后面的因数根本不会数。怎么答对:走读时把 break、continue、return 当作"断点"标出来,先问自己"这个断点会不会在到达终点前触发"。如果会,统计上限就是断点值,输出取min(实际值, 断点值)。
5.4 字符'0'和数字 0 混用:ASCII 码的经典陷阱
错在哪:给字符变量赋值时,把c = 0;当成赋字符'0'。为什么错:字符'0'的 ASCII 码是 48,数字 0 赋给字符变量得到的是空字符'\0',输出时不可见,但和'0'完全是两个东西。怎么答对:记住三个码——'0'是 48,'A'是 65,'a'是 97。题目问"哪个选项能让字符变量得到字符'0'",选c = '0';或c = 48;,凡是c = 0;和c = '\0';都排除。
5.5 二维数组下标顺序:行优先不是列优先
错在哪:int a[3][2]按列去数,把a[1][0]当成第一行第二列。为什么错:C++ 二维数组按行优先存储,第一个下标是行,第二个下标是列,初始化列表从上到下、从左到右填充。怎么答对:草稿上画一个 3 行 2 列的表格,把初始化列表里的数依次填进去,填完再对选项。这个方法慢十秒,但正确率接近百分之百,比在脑子里推可靠。
6. 答题技巧:40 分钟里怎么把分数稳在晋级线上
校级预选赛的时间通常是 40 到 60 分钟,题量 24 道单选,平均每题不到两分钟。这个节奏下,决定分数的不是会不会做难题,而是会不会分配时间。我的建议是把卷子按"送分—拉分—放弃"三档切:基础语法 12 道题控制在 15 分钟内做完,程序阅读 8 道给 20 分钟,问题阅读 4 道给 15 分钟,最后留 5 到 10 分钟专门复查易错点。下面是具体的执行顺序。
第一步,开考先扫一遍全卷,把一眼能选出答案的题(变量命名、进制转换、二维数组)先做掉,这些题不需要草稿,做完就在题号前打勾。第二步,程序阅读题按"先易后难"排序,计数类、gcd 类的先做,筛法、素数分支这类结构复杂的放后面,每道题强制在草稿上写三行:循环范围、提前退出语句、一个小输入的模拟结果。第三步,问题阅读题不要通读题干,先看四个选项,再回题干找对应的约束条件,用排除法做,四个选项排除三个比正面证明一个快得多。
| 题型 | 数量 | 建议用时 | 核心动作 |
|---|---|---|---|
| 编程基础知识 | 12 题 | 15 分钟 | 规则反射,不推导 |
| 程序阅读 | 8 题 | 20 分钟 | 草稿模拟,标边界和断点 |
| 问题阅读 | 4 题 | 15 分钟 | 排除法,先看选项 |
| 复查 | — | 5–10 分钟 | 专查边界、全局变量、ASCII |
复查环节只查三类东西,别从头再做一遍:第一查所有i < n和i <= n的题,第二查所有出现cnt没显式赋值的题,第三查所有涉及字符和数字混用的题。这三类占了易错点的八成,五分钟能捞回好几分。
最后说一个我自己的习惯。早年带学生做这类卷子,最容易翻车的不是难题,是程序阅读里那种"看着简单、结果算错"的计数题,明明会做,就是边界记反了。后来我让学生每次做程序阅读,不管多简单,都在草稿纸左上角写一行"变量:全局/局部;循环:起止;断点:有无",写完再动笔。这个动作看着笨,但坚持几次之后,边界看错和 break 漏看的问题基本就没了。你也可以把这个三行清单抄在草稿纸边上,开考先写,做完再划,一步一步走,分数就稳了。希望帮到你。
本文还有配套的精品资源,点击获取