这份卷子是我近期给备赛学生做训练时用的第3套模拟卷,今天整理成文,直接分享给大家。全国青少年信息素养大赛算法应用主题赛这个赛道,近几年报名热度一直很高,C++组初赛的题型相对稳定,主要考察三件事:语法基础扎不扎实、能不能读懂别人写的程序、以及面对一道基础算法题能不能在限时内写出能跑的代码。很多孩子是第一次参加,对初赛题型心里没底,刷真题又担心难度不合适,所以我干脆按近两年初赛常见命题风格编了一套模拟卷3,覆盖C++语法细节、STL容器、位运算、排序、二分查找和简单搜索,满分100分,难度略高于初赛平均线,适合已经学完语法、正在刷算法题的选手用来自测,带赛老师也可以直接当周测卷用。
1. 卷面设计与考点布局
1.1 为什么初赛卷要这样出
初赛和决赛不一样,决赛拼的是上机编程速度和正确率,而初赛更多是“筛选”。出题人要在有限时间内快速判断一个选手到底有没有系统学过C++、有没有基本的算法思维,所以题型通常分成三类:选择题考概念边界和语法细节,阅读程序题考手动模拟代码的能力,算法应用题考最基础的编码实现。这套模拟卷就完全按照这个逻辑来设计。8道单选题覆盖数组越界、递归、冒泡排序、位运算、STL容器、缓存局部性、二分细节、结构体排序,这些知识点在历年初赛里反复出现,做题时你会明显感觉到,题目不难,但每个选项都有陷阱,概念模糊的孩子特别容易踩坑。阅读程序题则选了循环累加、递归斐波那契、字符频率统计三个经典场景,难度循序渐进。三道算法应用题分别对应基础模拟、二分查找、全排列DFS,属于初赛编程题里最常出现的几种类型。
1.2 建议答题时间分配与做题顺序
这份卷子我建议按90分钟限时来训练。选择题控制在30分钟以内,平均每道题不超过4分钟,单选题遇到犹豫的先圈出来,不要死磕,后面时间多再回头想。阅读程序题给35分钟,这部分最忌讳用眼睛“虚空读代码”,一定要动手画表,逐行跟踪变量变化。最后25分钟留给三道算法应用题。很多学生习惯先做编程题,认为分值高,但我建议的顺序是:先阅读程序题再做选择题,因为阅读题需要清醒的脑子去手算,而选择题相对独立,放在状态下降时做容错率更高。编程题哪怕时间紧,也至少要把框架写出来,争取拿到部分分。
2. 题目构成与高频考点拆解
2.1 选择题:语法细节与STL陷阱
选择题看起来是送分题,实际上失分最严重。我见过太多孩子单独问语法都懂,一放到选择题里就选错,原因在于初赛选择题特别爱考“边界情况”和“未定义行为”。比如数组越界,很多老师上课会强调“不要越界”,但没人告诉你越界到底会发生什么,于是题目一改成“编译能过吗?运行时会怎样?”学生就懵了。还有递归,很多学生背过“递归必须有终止条件”,但对“没有终止条件会怎样”理解不深,不知道最终是栈溢出。这套卷子的选择题考点里,位运算x & (x - 1)是一个被低估的高频考点,它能把整数二进制表示中最低位的1变成0,很多进阶算法(比如树状数组、统计二进制中1的个数)都用它。STL容器特性也是个重点,尤其是set自动有序这一点,和vector、queue混在一起考,最能区分基础是否扎实。
2.2 阅读程序题:手算能力决定成败
阅读程序题考察的不是“你看没看懂”,而是“你算得对不对”。我常跟学生讲,代码是人写的,程序是机器跑的,读代码时你不能按照自己的心意替机器省略步骤。一个最实用的方法就是列变量跟踪表:把每个变量的初始值写在表头,然后逐行执行,每执行一行就更新一次表格。特别是递归程序,很多孩子一看递归就害怕,其实只要坚持“把递归调用一层一层展开”的手算方式,f(6)这种问题完全可以像剥洋葱一样剥出来。阅读程序题还有一个隐藏考点:输出格式。比如要不要换行、有没有空格,题目问的是“输出结果”而不是“程序功能”,手算时如果忽略细节,明明逻辑对了,写答案时却漏了换行符,照样丢分。这套卷子阅读题里我会专门安排一道统计字符出现次数的题,来帮你检查对数组下标的敏感度。
2.3 算法应用题:从“会思路”到“写得出、写得对”
很多学生在训练时面临一个尴尬:思路能说清楚,但一上机就编译报错或者边界条件处理不好。初赛的算法题通常不会出偏题怪题,重点在于你能不能把脑子里的思路转成一行行正确的代码。以二分查找为例,几乎人人都会说“二分查找就是不断折半”,但真正写的时候,while循环条件是l < r还是l <= r、mid到底取(l + r) / 2还是l + (r - l) / 2、区间是左闭右开还是左闭右闭,这些细节一个搞错就全错。所以算法应用题的备考建议很简单:平时刷题不要只看思路,一定要把完整代码敲进电脑里跑一遍,用样例去验证,再自己设计边界数据去测试。这个习惯培养起来,初赛编程题的基本分就稳了。
3. 模拟卷3正文(2026年算法应用主题赛C++初赛模拟卷3)
建议闭卷作答,限时90分钟,满分100分。
3.1 单项选择题(每题5分,共40分)
1.声明int a[5];之后,执行a[5] = 10;,下列说法正确的是( )
A. 编译错误,C++不允许数组下标越界 B. 编译可能通过,但运行属于未定义行为,可能修改未知内存 C. 数组会自动扩容,可以正常存储 D. 运行时一定会触发段错误
2.关于递归函数,下列说法正确的是( )
A. 递归函数必须返回 int 类型 B. 递归函数必须包含终止条件,否则会无限递归并最终导致栈溢出 C. 递归函数只能调用自己一次 D. 递归函数的运行效率一定高于循环
3.对序列{6, 3, 8, 2, 5}使用冒泡排序从小到大排序,第一趟排序完成后序列为( )
A.{3, 6, 2, 5, 8}B.{2, 3, 5, 6, 8}C.{6, 3, 2, 8, 5}D.{3, 2, 5, 6, 8}
4.对任意正整数 x,表达式x & (x - 1)的作用是( )
A. 判断 x 是否为偶数 B. 把 x 的二进制表示中最低位的 1 变成 0 C. 把 x 的二进制表示中最高位的 1 变成 0 D. 交换 x 的低位字节与高位字节
5.在STL中,向下列哪个容器插入元素后,容器中的元素会自动保持有序( )
A. vector B. list C. set D. queue
6.二维数组按行遍历比按列遍历通常更快,主要原因是( )
A. 按行遍历时CPU缓存命中率更高,能减少内存访问延迟 B. 按行遍历时循环次数更少 C. 二维数组只能按行遍历,按列遍历会编译失败 D. 按列遍历会发生数组越界
7.在二分查找的循环while (l <= r)中,相比int mid = (l + r) / 2;,更推荐的写法是( )
A.int mid = l + (r - l) / 2;B.int mid = (r + l) * 2;C.int mid = r / 2 + l / 2;D. 两种写法没有任何区别
8.阅读以下程序,输出是( )
#include <bits/stdc++.h> using namespace std; struct Stu { int id, score; }; bool cmp(Stu a, Stu b) { return a.score > b.score; } int main() { vector<Stu> v = {{1, 88}, {2, 95}, {3, 78}}; sort(v.begin(), v.end(), cmp); for (auto s : v) cout << s.id << " "; return 0; }A.1 2 3B.2 1 3C.3 2 1D.1 3 2
3.2 阅读程序题(每题10分,共30分)
阅读题1
#include <iostream> using namespace std; int main() { int sum = 0; for (int i = 1; i <= 10; i++) { if (i % 2 == 0) sum += i; } cout << sum << endl; return 0; }(1)输出结果:______
阅读题2
#include <iostream> using namespace std; int f(int n) { if (n <= 2) return 1; return f(n - 1) + f(n - 2); } int main() { cout << f(6) << endl; return 0; }(2)输出结果:______
阅读题3
#include <iostream> #include <string> using namespace std; int main() { string s = "hello"; int cnt[26] = {0}; for (int i = 0; i < (int)s.size(); i++) { cnt[s[i] - 'a']++; } int ans = 0; for (int i = 0; i < 26; i++) { if (cnt[i] > 0 && cnt[i] % 2 == 0) ans++; } cout << ans << endl; return 0; }(3)输出结果:______
3.3 算法应用题(每题10分,共30分)
第1题:字符频率统计
输入一行只包含小写字母的字符串,长度不超过100,按字典序输出每个出现过的字母及其出现次数,每行格式为字母:次数。
输入样例:
hello输出样例:
e:1 h:1 l:2 o:1第2题:二分查找(程序填空)
给定一个升序排列的整数数组a,长度 n 不超过 100000,有 q 次询问,每次询问一个整数 x,请输出 x 在数组中第一次出现的位置(下标从 0 开始)。如果 x 不存在,输出 -1。
已有程序框架如下,请在空格处补全二分查找逻辑。
#include <bits/stdc++.h> using namespace std; int a[100005]; int lower_bound_custom(int n, int x) { int l = 0, r = n; // 左闭右开区间 [l, r) while (l < r) { int mid = ________; // 第一空:求中点 if (________) // 第二空:判断 a[mid] 与 x 的关系 r = mid; else l = mid + 1; } return l; } int main() { int n, q; cin >> n >> q; for (int i = 0; i < n; i++) cin >> a[i]; while (q--) { int x; cin >> x; int pos = lower_bound_custom(n, x); if (pos != n && a[pos] == x) cout << pos << endl; else cout << -1 << endl; } return 0; }输入样例:
5 3 1 3 3 5 7 3 4 7输出样例:
1 -1 4第3题:排列生成
输入一个正整数 n(1 ≤ n ≤ 8),按字典序输出 1 到 n 的所有全排列,每个排列占一行,数字之间用一个空格隔开。
输入样例:
3输出样例:
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 14. 参考答案与逐题解析
4.1 选择题答案速查与解析
| 题号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 答案 | B | B | A | B | C | A | A | B |
第1题解析:C++不会检查普通数组的越界,所以编译阶段通常不会报错。但运行时越界访问是未定义行为,可能没有明显问题,也可能悄悄改写了相邻内存,甚至导致程序崩溃,因此不要抱有侥幸心理,从编码习惯上就应该杜绝越界。
第2题解析:递归就像套娃,每一层调用都会占栈空间,如果没有终止条件,栈会被一层层塞满直到溢出,程序崩溃。递归的终止条件就是“最小的那个套娃”,必须最先想清楚。
第3题解析:冒泡排序第一趟从左往右两两比较,6和3交换,6和8不交换,8和2交换,8和5交换,最大的8被“冒”到最后,所以结果是{3, 6, 2, 5, 8}。这个考点不仅考排序过程,还考“第一趟结束后最大值一定在末尾”这个性质。
第4题解析:x & (x - 1)是经典位运算技巧。x - 1会把x最低位的1变成0,并把之后的所有0变成1,再与x做按位与,最低位的1就被抹掉了。例如x = 12(二进制1100),x - 1 = 11(1011),按位与得到1000(8)。
第5题解析:set内部使用红黑树实现,插入元素后会自动按排序规则排列。vector和list都是线性容器,不会自动排序。queue是队列,只保证先进先出,更谈不上有序。
第6题解析:C++二维数组按行存储,按行遍历时访问的地址连续,CPU缓存命中率高很多,所以速度快。这也是为什么算法题里处理矩阵时,尽量让内层循环按连续内存方向访问。
第7题解析:当 l 和 r 都很大时,l + r可能超过int范围导致溢出。l + (r - l) / 2用差值代替求和,从根本上避免了溢出问题,等价但更安全,是竞赛中推荐的标准写法。
第8题解析:sort传入自定义比较函数cmp,return a.score > b.score表示按分数从高到低排序。95分、88分、78分对应id 2、1、3,所以输出2 1 3。
4.2 阅读程序题解析
阅读题1的答案是30。程序从1循环到10,把其中所有偶数累加,也就是 2 + 4 + 6 + 8 + 10 = 30。这道题是送分题,但能帮你检查循环变量取值范围,i <= 10和i < 10是初赛特别喜欢埋的陷阱。
阅读题2的答案是8。f(6)是斐波那契数列,递推关系为f(n)=f(n-1)+f(n-2),边界是f(1)=1、f(2)=1。手算时可以按层级展开:f(6)=f(5)+f(4)=(f(4)+f(3))+(f(3)+f(2)),继续展开到边界,最终得到8。注意不要心算跳步,写清楚展开式能有效减少出错。
阅读题3的答案是1。程序统计字符串 "hello" 中每个字母出现的次数:h出现1次,e出现1次,l出现2次,o出现1次。然后统计“出现次数为偶数的字母种类”,只有l是2次,所以ans=1。这道题重点考察两个细节:一是s[i] - 'a'这个把字符映射到数组下标的操作,这是字符统计题的通用套路;二是“偶数字母种类”而不是“偶数个字母”,很多孩子看题快了就数成4个字母。
4.3 算法应用题参考代码与得分要点
第1题参考代码:
#include <bits/stdc++.h> using namespace std; int main() { string s; cin >> s; int cnt[26] = {0}; for (int i = 0; i < (int)s.size(); i++) { cnt[s[i] - 'a']++; } for (int i = 0; i < 26; i++) { if (cnt[i] > 0) { cout << char('a' + i) << ":" << cnt[i] << endl; } } return 0; }得分要点:数组要初始化为0;输出时按字典序枚举a到z,而不是按字母在字符串中出现的先后顺序输出。我见过不少孩子把输出写成“遍历原字符串,每个字符立即输出次数”,结果重复输出,比如hello会输出两次l。正确做法是先统计到cnt数组里,再统一遍历26个下标输出。
第2题参考答案:
第一空:l + (r - l) / 2
第二空:a[mid] >= x
完整逻辑说明:这是标准的左闭右开二分。当a[mid] >= x时,说明第一个大于等于x的位置不可能出现在mid右边,所以把右边界收缩到mid;否则左边界移动到mid+1。循环结束后,l指向第一个大于等于x的位置。主函数里再判断a[pos] == x,就能确定x是否存在。这个写法等价于STL的lower_bound,初赛和复赛都经常用,建议背熟。
第3题参考代码:
#include <bits/stdc++.h> using namespace std; int n, path[10]; bool used[10]; void dfs(int step) { if (step == n) { for (int i = 0; i < n; i++) { if (i) cout << " "; cout << path[i]; } cout << endl; return; } for (int i = 1; i <= n; i++) { if (!used[i]) { used[i] = true; path[step] = i; dfs(step + 1); used[i] = false; } } } int main() { cin >> n; dfs(0); return 0; }得分要点:DFS搜索全排列时,路径数组path和标记数组used是标配。记得回溯时把used[i]重新设为false,否则下一个分支就没办法使用这个数字了。输出格式要求“数字之间用一个空格隔开”,行末不能有多余空格,所以我用if (i) cout << " "先在每个非首元素前输出空格。n最大只有8,递归深度最多8层,完全不用担心性能。
5. 从这份卷子暴露出的问题聊赛前冲刺
5.1 初赛踩坑实录与排查技巧
每次我做模拟卷批改,都会把学生错误整理成一张“高频踩坑清单”,这份卷子同样适用。下面这些坑基本是初赛里出现频率最高的:
| 问题类型 | 典型错误 | 排查与改进方法 |
|---|---|---|
| 数组越界 | 访问a[n]而不是a[n-1] | 写循环前先确认数组区间,记住C++下标从0开始 |
| 变量未初始化 | 计数器sum忘记赋0 | 养成定义变量就立刻初始化的习惯 |
| 输入读取 | cin读取字符串遇空格截断 | 如果题目字符串可能含空格,改用getline |
| 递归边界 | 递归调用时漏掉终止条件 | 写递归函数先问自己“最小规模怎么返回” |
| 二分细节 | 循环条件和区间开闭搞混 | 固定使用一种模板,不要三套模板混着记 |
| 排序规则 | cmp函数返回值理解反 | 记住less是升序,greater是降序,自定义时用例子验证 |
| 输出格式 | 行末多一个空格、漏掉换行 | 提交前用样例测试,肉眼对比输出 |
5.2 最后两周的“性价比”复习策略
如果距离初赛只剩两周,我的建议是不要盲目刷难题,而是把重心放在“基础题零失误”上。具体来说分两条线走。第一条线:把这份卷子里的选择题每个考点都彻底搞懂,如果你发现set为什么有序、位运算为什么会清掉最低位的1这类概念还要翻书,那就把对应的STL容器和位运算专题重新过一遍,找几道简单题练手。第二条线:把二分查找和全排列DFS这两道题背到“条件反射”的程度,因为初赛编程题大概率涉及其中一个,而这两个题型的模板非常固定,只要背熟并理解,基本分就拿到了。每天花一到两个小时上机,把模板代码敲一遍,再用不同输入样例测试,比反复看教程有效得多。
5.3 一些带赛过程中沉淀下来的心得
带学生备赛这几年,我越来越觉得初赛刷题不在多,在“复盘”。一份卷子做完,对照答案不算完,要把每道错题旁边写清“我为什么错”和“下次怎么避免”,这才是这份模拟卷最大的价值。很多孩子同一类错误会犯三次,第一次是不知道,第二次是不小心,第三次就是没有认真复盘。另外,上机编程时一定要养成自己造测试数据的习惯,比如做字符统计就试一下只有1个字符的情况、26个字母全出现的情况,做二分就试一下x比所有数都小、x比所有数都大的情况,边界数据一测,很多隐藏bug就现形了。竞赛到最后拼的不只是智商,更是严谨和习惯。
这套模拟卷如果能限时完成并稳定拿到85分以上,说明你的C++基础和算法应用能力已经比较扎实,可以安心进入决赛备赛阶段。我个人在实际训练中还会要求学生把错题对应的知识点做成一个小卡片,考前翻一遍,比临考抱佛脚刷题要轻松得多,也踏实得多。