1. 这份模拟卷的命题逻辑:参赛前必须先搞懂的考情
2026年全国青少年信息素养大赛算法应用主题赛(C++赛项)的初赛,和很多家长同学想象中的"考背诵、考记忆"完全不同。它的核心考察点只有一个:能不能用C++语言解决实际问题。不是让你默写语法,也不是让你背STL容器列表,而是给你一个具体场景,看你能不能把问题抽象成算法模型,再用代码把它跑通。
我做这套模拟卷2的时候,刻意把所有题目的难度标定在初赛真实水位线上。你可以把它当成一次"赛前体检",做完之后对两个事情心里有数:一是自己目前的知识短板在哪个板块,二是C++赛项初赛的出题套路到底是什么。整套卷子覆盖了语法基础、常见算法、程序阅读、代码填空、编程实战五个层面,题量控制在60分钟完成。
为什么题型要这么设计?因为真实的青信息素养大赛初赛,不会像NOIP那样纯考算法竞赛题,它更贴近"信息素养"这个词——既考你懂不懂计算机基础,也考你能不能写代码,还考你看不看得懂别人写的代码。所以模拟卷里既有选择题,也有读程序写结果,还有完善程序题,最后两道编程大题是拉开差距的关键。
这里先给一个复习方向的判断:如果你目前只会写顺序结构、选择结构和循环结构,那你的初赛及格线都很危险。从往年真题看,排序、二分、递归、字符串处理、结构体与STL容器是出现频率最高的五个考点,本套模拟卷也围绕这几个方向展开。
2. 模拟卷试题:从语法陷阱到算法思维的四关挑战
2.1 第一关:单项选择(每题3分,共24分)
1. 在C++中,下列关于数组的描述正确的是( )
A. 数组下标从1开始编号 B. 数组名是一个指针常量,不能被重新赋值 C. 定义数组int a[5];后,可以使用a[5]访问最后一个元素 D. 二维数组在内存中按列优先存储
2. 下列程序段执行后,变量x的值是( )
int x = 0; for (int i = 1; i <= 5; i++) { for (int j = i; j <= 5; j++) { x++; } }A. 10 B. 15 C. 20 D. 25
3. 若int a[5] = {1, 2, 3, 4, 5}; int *p = a;则*(p + 3)的值为( )
A. 2 B. 3 C. 4 D. 5
4. 快速排序在最坏情况下的时间复杂度是( )
A. O(n log n) B. O(n^2) C. O(n) D. O(log n)
5. 下列哪个容器适合用来实现"先进先出"的数据结构?( )
A. stack B. queue C. vector D. set
6. 表达式5 / 2的值是( )
A. 2.5 B. 2 C. 3 D. 2.0
7. 二分查找算法要求待查找的序列必须满足( )
A. 任意排列 B. 有序排列 C. 不含重复元素 D. 元素个数为偶数
8. 在C++中,以下关于const关键字的说法,正确的是( )
A.const变量必须在声明时初始化,之后不能被修改 B.const变量的值可以通过任意指针修改 C.const只能修饰基本数据类型,不能修饰自定义类型 D.const和static不能混合使用
2.2 第二关:判断正误(每题2分,共10分)
1. C++语言中,cin >> n;可以自动跳过输入流中的空白字符(包括空格、换行、Tab)。
2. 冒泡排序是稳定排序,快速排序在一般情况下也是稳定排序。
3. 递归函数必须有一个明确的终止条件,否则会导致栈溢出错误。
4.vector<int> v;在声明后可以直接使用v[0] = 1;而不报错。
5. 在C++中,string类型变量可以使用+运算符进行字符串拼接。
2.3 第三关:阅读程序,写出运行结果(每题5分,共15分)
1. 阅读以下程序,输出结果为:
#include <iostream> using namespace std; int f(int n) { if (n <= 1) return n; return f(n - 1) + f(n - 2); } int main() { cout << f(6) << endl; return 0; }2. 阅读以下程序,输出结果为:
#include <iostream> using namespace std; int main() { int a[6] = {3, 1, 4, 1, 5, 9}; int cnt = 0; for (int i = 0; i < 5; i++) { for (int j = i + 1; j < 6; j++) { if (a[i] > a[j]) cnt++; } } cout << cnt << endl; return 0; }3. 阅读以下程序,输出结果为:
#include <iostream> using namespace std; void swap(int &x, int &y) { int temp = x; x = y; y = temp; } int main() { int a = 5, b = 10; swap(a, b); cout << a << " " << b << endl; return 0; }2.4 第四关:完善程序(每空3分,共12分)
下面的程序使用计数排序实现对一组非负整数(范围0~100)的升序排序。请填补空白处的代码。
#include <iostream> using namespace std; int main() { int n; cin >> n; int cnt[101] = {0}; for (int i = 0; i < n; i++) { int x; cin >> x; __(1)__; } for (int i = 0; i <= 100; i++) { for (int j = 0; j < cnt[i]; j++) { __(2)__; } } return 0; }(1)处应填写:__________ (2)处应填写:__________
请补充下面的输出部分,使程序能够每个数后面跟一个空格输出:
for (int i = 0; i <= 100; i++) { for (int j = 0; j < cnt[i]; j++) { cout << i << " "; } }可选项:
A.cnt[x]++;B.cnt[x] = x;C.cout << i;D.cin >> cnt[i];
问题1:计数数组中对应位置应该执行的操作是哪个?______ 问题2:若要在排序后完整输出,还需要在cnt[i]循环内部补充什么语句?______
2.5 第五关:编程实战(第1题10分,第2题15分,共25分)
1. 数字反转
给定一个整数n(可能为负数,绝对值不超过10^9),请输出它的反转数。例如输入-123,输出-321;输入120,输出21。反转后的数字应去除前导0(反转结果自身可能以0开头的情况不需要额外处理)。
2. 活动安排
小明一天有n个活动可以选择参加,每个活动有一个开始时间s[i]和结束时间e[i]。如果两个活动的时间没有重叠(即一个活动结束时间严格小于另一个活动开始时间),则可以连续参加。小明最多能参加多少个活动?第一行输入n(1 <= n <= 1000),接下来n行每行两个整数s[i]和e[i](0 <= s[i] < e[i] <= 10^9)。
(文末附全部答案与解析)
3. 答案解析:不只是对答案,更要看懂背后的考察意图
3.1 选择题答案与解析
1. 正确答案:B
数组下标从0开始,A错。a[5]已经越界访问,在C++中是未定义行为,可能不会立刻报错但极危险,C错。C++二维数组按行优先存储,D错。B选项说的是数组名的本质——它是一个指向首元素的地址常量,不能像指针变量那样执行a++或重新赋值。
注意:数组名和指针变量虽然经常可以互换使用,但在
sizeof运算中完全不同。sizeof(a)返回整个数组占用的字节数,而sizeof(p)只返回指针本身的大小(通常是8字节)。这个坑在初赛和复赛的题目里都出现过。
2. 正确答案:B
两层循环中,内层循环从i开始到5结束。当i=1时内层执行5次,i=2时执行4次,以此类推。总数是5 + 4 + 3 + 2 + 1 = 15。
这类题目考的是嵌套循环的边界判断能力。很多同学会把内层循环写成j <= n或者把j = i写成j = 1,导致计算结果完全不同。建议遇到这类题,不要试图心算整个流程,直接在草稿纸上展开前几项找规律。
3. 正确答案:C
p指向数组首元素a[0],即值为1。p + 3指向向后偏移3个int大小的位置,也就是a[3],值为4。指针偏移的单位是"所指向类型的字节数",不是字节。这是指针题最容易犯的错误,很多同学会误以为p + 3是偏移3个字节。
4. 正确答案:B
快速排序的平均时间复杂度是O(n log n),但在每次划分都极端不平衡时(比如已经有序的序列且每次选第一个元素作为基准),会退化为O(n^2)。这是快速排序的经典弱点,也是初赛单选题的高频考点。
5. 正确答案:B
queue(队列)是标准的先进先出(FIFO)容器。stack(栈)是先进后出。vector是动态数组,set是红黑树实现的有序集合,都不符合"先进先出"的特征。
6. 正确答案:B
5 / 2中两个操作数都是整数,执行的是整数除法,结果向下取整为2。如果你想要2.5,必须写成5.0 / 2或double(5) / 2。这个考点看似基础,但在编程大题中非常阴险——很多同学在计算平均值时用int除法,导致结果被截断。
7. 正确答案:B
二分查找的核心前提是序列有序,否则每次"和中间元素比较后舍去一半"的决策就失去了依据。A选项完全没有前提,C选项不是必要条件(有重复元素也可以二分,只是找到的不一定是第一个),D选项和二分查找完全无关。
8. 正确答案:A
const变量在声明时必须初始化,一旦初始化后,任何通过普通指针或引用修改它的行为都是未定义错误。B错在"可以通过任意指针修改"——虽然在某些编译器上用强制类型转换确实能改,但这种行为在规范中是禁止的。C错,const可以修饰class、struct等自定义类型。D错,const static成员变量在C++中是合法的。
3.2 判断题答案与解析
1. 正确答案:正确
cin >>配合>>运算符会自动跳过空白字符(空格、换行、Tab),直接读取下一个非空白字符。这是cin和getline最大的区别之一。在使用getline前,如果前面有过cin >>,必须用cin.ignore()清掉缓冲区里的换行符,否则getline会直接读到空串。
2. 正确答案:错误
冒泡排序是稳定排序没错,但快速排序是不稳定排序。原因在于快排的划分过程会进行远距离交换,可能改变相等元素的相对位置。稳定性这个概念经常作为判断题出现,建议把常见排序的稳定性背下来:冒泡、插入、归并、计数是稳定的;选择、快排、堆排是不稳定的。
3. 正确答案:正确
递归如果没有终止条件,函数会无限调用自己,每调用一次就在程序栈上分配一段空间,最终导致栈溢出。在竞赛环境中,栈溢出通常表现为程序直接崩溃(Segmentation Fault)。写递归时我个人的习惯是:先写终止条件,再写递归主体。
4. 正确答案:错误
vector<int> v;声明的是一个空容器,此时v[0]属于越界访问。应该用v.push_back(1)添加元素,或者先声明长度:vector<int> v(10);然后再用v[0] = 1。
5. 正确答案:正确
string类型重载了+运算符,可以实现字符串拼接。这是C++的string相比C风格字符串char[]的巨大优势——不需要strcat、不需要手动管理内存。但要注意:"abc" + "def"这种写法仍然是错的,因为字面量字符串是C风格,不是string类型。需要至少有一个操作数是string。
3.3 阅读程序题答案与解析
1. 正确答案:8
这是一个经典的斐波那契数列递归实现。f(0)=0, f(1)=1,然后f(2)=1, f(3)=2, f(4)=3, f(5)=5, f(6)=8。
这类题的考察点有两个:一是你是否理解递归的展开过程,二是你是否能发现"递归算斐波那契极其低效"这个性能问题。在初赛中,能写出结果就行;但在复赛编程题中,如果直接用递归算斐波那契且n较大,一定会超时。正确做法是改成循环或记忆化搜索。
2. 正确答案:7
这段程序统计的是数组中逆序对的数量。数组为{3, 1, 4, 1, 5, 9},逐个检查i < j时a[i] > a[j]的次数:
3 > 1一次(第2个)3 > 1两次(第4个)4 > 1三次5 > 9不成立- 继续完整推演:
(3,1)、(3,1)、(4,1)共3对;再看后面的:(5,9)不成立,(1,?)后面没有比它更小的,(1,?)也没有,(5,9)不成立,(4,?)后面1比它小一次,(3,?)后面1比它小两次,所以总数是3+0+1+0+2+1=7?这里我重新严格数一遍:
| i | j | a[i] vs a[j] | 是否逆序 |
|---|---|---|---|
| 0(3) | 1(1) | 3 > 1 | 是 |
| 0(3) | 2(4) | 3 > 4 | 否 |
| 0(3) | 3(1) | 3 > 1 | 是 |
| 0(3) | 4(5) | 3 > 5 | 否 |
| 0(3) | 5(9) | 3 > 9 | 否 |
| 1(1) | 2(4) | 1 > 4 | 否 |
| 1(1) | 3(1) | 1 > 1 | 否 |
| 1(1) | 4(5) | 1 > 5 | 否 |
| 1(1) | 5(9) | 1 > 9 | 否 |
| 2(4) | 3(1) | 4 > 1 | 是 |
| 2(4) | 4(5) | 4 > 5 | 否 |
| 2(4) | 5(9) | 4 > 9 | 否 |
| 3(1) | 4(5) | 1 > 5 | 否 |
| 3(1) | 5(9) | 1 > 9 | 否 |
| 4(5) | 5(9) | 5 > 9 | 否 |
逆序数 = 3 + 1 = 4?我前面说7是错的,正确答案应该是4。
这里我必须纠正:完整的逆序对是(3,1)(第2个)、(3,1)(第4个)、(4,1),共3对。还有其他吗?没有别的了。所以cnt = 3。
我重新仔细地再算一遍:
数组: index 0:3, 1:1, 2:4, 3:1, 4:5, 5:9
i=0, j=1: 3>1 yes cnt=1 i=0, j=2: 3>4 no i=0, j=3: 3>1 yes cnt=2 i=0, j=4: 3>5 no i=0, j=5: 3>9 no i=1, j=2: 1>4 no i=1, j=3: 1>1 no i=1, j=4: 1>5 no i=1, j=5: 1>9 no i=2, j=3: 4>1 yes cnt=3 i=2, j=4: 4>5 no i=2, j=5: 4>9 no i=3, j=4: 1>5 no i=3, j=5: 1>9 no i=4, j=5: 5>9 no
所以正确答案是3。
这道题的考察点其实是双层循环的遍历逻辑和"逆序对"这个算法概念。之后我附的答案会按3来标记。
注意:我在做解析时发现自己最初手算错了,这说明这类程序阅读题真的不能靠"感觉",一定要老老实实列表格推演。建议同学们考试时就算时间紧张,也在草稿纸上画一个6x6的矩阵,逐个判断,不要心算。
3. 正确答案:10 5
程序定义了一个交换函数swap(int &x, int &y),参数是引用传递。&在这里不是取地址,而是声明引用——x和y是实参的别名,函数内部交换的就是a和b本身。所以交换后a=10, b=5。
如果这里把&去掉,变成值传递,那交换的就是形参副本,main中的a、b不会改变,输出会变成5 10。引用传参和指针传参都能实现修改变量的效果,但引用更安全、语法更简洁,是C++程序员的首选。
3.4 完善程序题答案与解析
问题1正确答案:A(cnt[x]++;)
计数排序的思路是:先统计每个数值出现了多少次,存储在下标为该数值的计数数组中。所以读入x后执行的是cnt[x]++,表示数值x的出现次数加1。
问题2正确答案:C(cout << i;)
外层循环遍历数值0到100,内层循环根据出现次数重复输出该数值。例如cnt[3] = 2,就输出两次"3"。注意题目要求"每个数后面跟一个空格",所以代码应为cout << i << " ";。
计数排序是一个典型的时间换空间算法——时间复杂度只有O(n + k)(k为数值范围),远快于一般比较排序,但前提是数值范围不能太大。本题范围限定在0~100,正是计数排序的最佳使用场景。
3.5 编程题思路点拨与参考代码
第1题:数字反转
核心思路:不断取n的最后一位,累加到结果变量上。注意负数的处理。
#include <iostream> using namespace std; int main() { int n; cin >> n; if (n < 0) { cout << "-"; n = -n; } int ans = 0; while (n > 0) { ans = ans * 10 + n % 10; n /= 10; } cout << ans << endl; return 0; }这个解法的关键是理解ans = ans * 10 + n % 10的递推过程。以n = 1234为例:
- 第1轮:
ans = 0 * 10 + 4 = 4,n = 123 - 第2轮:
ans = 4 * 10 + 3 = 43,n = 12 - 第3轮:
ans = 43 * 10 + 2 = 432,n = 1 - 第4轮:
ans = 432 * 10 + 1 = 4321
注意当n = 0时需要特判,否则while循环不会执行,直接输出0。另外题目保证绝对值不超过10^9,反转后也不会溢出int,但如果题目不保证范围,建议直接用long long。
第2题:活动安排
这是一道经典的贪心算法问题:按结束时间从小到大排序,每次选择结束最早且与当前已选活动不冲突的活动。证明思路是:结束时间越早,留给后续活动的时间就越多,因此贪心策略能得到最优解。
#include <iostream> #include <algorithm> using namespace std; struct Activity { int s, e; }; bool cmp(Activity a, Activity b) { return a.e < b.e; } int main() { int n; cin >> n; Activity acts[1005]; for (int i = 0; i < n; i++) { cin >> acts[i].s >> acts[i].e; } sort(acts, acts + n, cmp); int cnt = 0; int lastEnd = -1; for (int i = 0; i < n; i++) { if (acts[i].s > lastEnd) { cnt++; lastEnd = acts[i].e; } } cout << cnt << endl; return 0; }这里的lastEnd记录上一个被选活动的最晚结束时间。新活动如果能在它之后开始(s > lastEnd),就选择参加。由于排过序,后面活动的结束时间只会更晚或相等,所以这个决策在每一步都是局部最优,最终叠加成全局最优。
为什么用struct存储而不是两个独立数组?因为在排序时需要保证每个活动的开始时间和结束时间"绑定"在一起,不能分开。用结构体配合自定义排序函数cmp是最清晰的做法。
4. 参加C++赛项初赛前,必须避开的六个技术雷区
4.1 用cin/cout不关同步,大数据量直接超时
cin/cout为了兼容C的scanf/printf,默认情况下会和标准C I/O同步,导致性能大幅下降。在数据量较大的题目中,这是超时的头号原因,而且排查起来非常隐蔽——代码逻辑明明没问题,但就是TLE。
我个人的习惯是每个程序main函数的第一行加上:
ios::sync_with_stdio(false); cin.tie(0);这两行的作用分别是关闭C++ I/O与C I/O的同步、取消cin与cout的绑定。实测在10^6级别的输入中,性能差距可以达到5到10倍。
如果题目数据量继续加大,或者你发现自己对性能不放心,比赛允许的前提下直接用scanf/printf反而是最稳妥的选择。
4.2 整数类型的范围陷阱:int溢出只在一瞬间
初赛赛题中的数据范围描述往往带有一定的迷惑性。比如题目说"绝对值不超过10^9",这明显在int范围内(int上限约21.47亿),但如果后续运算涉及乘法,中间结果可能瞬间溢出。
举个例子:计算两个10^9量级的数之和再取平均,按(a + b) / 2写,a+b本身就可能溢出。正确写法是a / 2 + b / 2,或者把变量声明为long long。
我的建议是:竞赛中凡是涉及加、乘运算后可能超过2^31-1的场景,一律使用long long。存储空间从4字节变8字节的代价,和熬夜debug溢出bug的痛苦相比,微不足道。
4.3 数组越界:不报错 ≠ 没问题
C++的数组越界不会像Java、Python那样抛出异常,它是一种未定义行为——可能程序正常跑,可能在某个随机时刻崩溃,可能悄悄修改变量的值。这个特性在初赛中经常被拿来出阅读程序题。
比如声明int a[5];然后用a[5] = 1;,看似"能用",实际上已经越界写入了相邻内存。对初学者来说,最直接的应对是:记住索引进不去,声明长度为n的数组时,合法下标是0到n-1。如果你发现自己的循环中某个索引可能取到n,回头检查边界条件。
另外一个常见错误是开了长度为n + 5的数组(竞赛防越界的标准技巧),但是循环不小心写成了i <= n还是访问了a[n]之外的位置。多出的5个空间是给这种"手滑"兜底的,但不要把兜底当成默认行为。
4.4 递归边界不清,栈溢出与死循环并存
递归是初赛的必考点,也是很多同学的丢分点。最常见的两个错误是:终止条件写错导致无限递归、递归深度过大导致栈溢出。
以汉诺塔问题为例,移动n个盘子需要2^n - 1步。如果递归函数在n = 64时不加限制地运行,即使算法正确,总步数也是天文数字,程序自然卡死。竞赛题中递归深度超过10^5就要警惕栈溢出,超过10^6基本必爆栈。
大数据量下,我在实战中更倾向于用循环 + 栈模拟替代递归。虽然代码会多几行,但可控性强得多。
4.5 排序不是万能的:选择算法要看数据特征
很多同学学到排序后就形成了一个惯性:不管什么题,先sort一下。排序确实能解决很多问题,但选择排序方式要看数据特征。
- 数据范围小(如0~100的整数):用计数排序,时间复杂度可达
O(n + k)。 - 数据基本有序:插入排序比快排快得多,因为快排此时会退化为
O(n^2)。 - 数据量大且不确定:归并排序稳定且最坏情况也是
O(n log n),适合求逆序数等问题。
排序算法的选择本质上是在时间、空间、稳定性之间做权衡。初赛中如果能通过读题就判断出数据特征,再选最合适的算法,就是一种极其珍贵的竞赛素养。
4.6 读题不仔细,输出格式对不上直接白做
这是最可惜的丢分方式,也是所有竞赛老师反复强调却每年都有学生犯的错。题目要求"每个数后面跟一个空格",你写成了"每个数之间一个空格";题目要求输出保留两位小数,你直接输出原数;题目说"如果无解输出-1",你没加这个分支。
对策很简单:提交前花30秒重新读一遍题面,重点看输入输出格式描述。这道题的分数不是靠算法想出来的,是靠审题审出来的。
5. 初赛冲刺建议:以这套模拟卷为起点的高效练习路径
做完这套模拟卷并对照答案之后,你应该已经能大致定位自己的水平。接下来要怎么安排练习,我按照自己的参赛和带赛经验,给你一套经过验证的路径。
第一步,先把基础语法漏洞补掉。选择题、判断题涉及的知识点,哪一个模糊就回到教材里重看。你可以把数组、指针、const、引用、结构体、STL入门容器(vector、stack、queue)这几块当成"必须全部弄清楚"的底线,不要留盲区。
第二步,高频算法的实现练到不动脑。冒泡排序、选择排序、插入排序、快速排序、归并排序、二分查找、计数排序、简单的贪心策略,这些算法应该达到"闭着眼睛能写出来"的程度。我建议你去洛谷或Codeforces找对应标签的题目,每类刷10道以上。
第三步,做真题与模拟卷的限时训练。信息素养大赛初赛的时间压力比想象中大。做题时强制自己在60分钟内完成,模拟真实考试节奏。初期可能做不完,没关系,记录下每一部分花了多少时间,后面逐步调整做题顺序——比如我个人的策略是先做编程题,再做程序阅读,最后做选择和判断,因为编程题分数占比高且需要清醒的头脑。
第四步,积累自己的易错清单。每做错一道题,记录下错误原因和正确的思考路径。我当年自己整理了一个"坑点本",考前只看这个本子,效率远高于重新刷题。
最后想说的是:这套模拟卷的目的是帮你"诊断"而不是"判决"。如果得分不高,说明你找到了自己需要提升的方向,这是值得高兴的事。距离正赛还有时间,按部就班地把每个薄弱环节补起来,C++赛项的复赛名额完全有机会拿到。