简介:针对2024全国青少年信息素养大赛华中赛区(河南)初赛C++科目整理的复习资料,面向参赛学生和带队教师,适合在赛前用来熟悉题型、巩固基础语法与常见算法。压缩包内为1个doc文档,整体仅55KB,内容集中,便于快速查阅、打印及按知识点对照练习。文档围绕初赛高频考点展开,已覆盖数字出现次数统计、完全数寻找、运算符优先级、质数判断、数据类型关键字、基本语法结构、代码补全及选择题解析等核心知识,并配有原题形式的填空补全与单选训练,可帮助读者逐项检验程序逻辑和C++语言掌握程度。目前已有483人学习,尤其适合在赛前冲刺阶段做针对性刷题、查漏补缺,也可以直接作为教师组织模拟练习的素材。
1. 2024全国青少年信息素养大赛华中赛区(河南)初赛C++:一场淘汰率不低的资格赛
2024全国青少年信息素养大赛华中赛区(河南)初赛C++,表面看是一场编程竞赛,实际更像资格筛选:题目不难,淘汰率却高,丢分多来自语法不牢、读题太急、输出格式出错。反直觉的是,只学半学期 C++ 的学生往往比刷难题的更容易过线——初赛考的是「稳」。
我见过有人刷过进阶算法,却栽在 int 溢出和多余空格上;有人只把循环、字符串、模拟题练透,照样晋级。这个方向适合小学高年级到初中的编程初学者,也适合带赛老师和陪练家长拿来检验阶段成果。
下面按四个问题展开:初赛考什么、语法怎么练成肌肉记忆、典型编程题从读题到 AC 的完整流程、最容易丢分的五个现场。代码可直接抄练,最后一周的冲刺清单在文末。
2. 初赛 C++ 考点地图:客观题、编程题与备赛环境
初赛考什么直接决定你练什么。信息素养大赛的初赛一般分客观题和编程题两部分:客观题覆盖语法概念和算法常识,编程题要求当场写出可运行代码。别一上来就刷难题,先把考点地图画清楚,你会发现要准备的范围比想象中窄得多。
2.1 客观题:语法、数据结构与算法常识的核心知识点
客观题包含选择题、判断题和「读程序写结果」,覆盖范围相当固定:基本数据类型与运算符、分支与循环、数组与字符串、函数与递归,外加少量算法与 STL 概念。其中读程序题占比最高,它考的不是能不能写出代码,而是能不能像计算机一样逐行执行代码。
| 知识块 | 常考形式 | 需要掌握到什么程度 |
|---|---|---|
| 数据类型与运算符 | 选择题、判断题 | 记清 int/long long 范围、整除与取模、运算优先级 |
| 分支与循环 | 读程序写结果 | 能手推 for/while 执行次数,分清 break 与 continue |
| 数组与字符串 | 读程序、找错 | 下标从 0 开始、越界后果、字符串结束符 |
| 函数与递归 | 读程序 | 参数传递方式、递归展开次数 |
| 排序与查找 | 选择题 | 冒泡/选择/插入流程,二分查找前提 |
| STL 容器 | 选择题 | vector/string/map/set 的增删查复杂度 |
读程序题最忌讳凭感觉跳步。我的做法是纸笔模拟:每个变量一行,循环每执行一次更新一次。看下面这个例子:
#include <iostream> using namespace std; int main() { int s = 0; for (int i = 1; i <= 10; i++) { if (i % 3 == 0) continue; s += i; } cout << s << endl; return 0; }手推一遍:i 从 1 到 10,遇到 3、6、9 时 continue 跳过累加,其余依次加上,得到 1+2+4+5+7+8+10 = 37。如果只扫一眼循环范围就猜结果,很容易把 continue 的作用漏掉。这类题用小数据练到不用思考就能推出,客观题基本就稳了。另一个常考点是运算符优先级,比如a + b > c实际上先算a + b再比较,选择题里经常有人在这里丢分。
2.2 编程题:信息素养大赛初赛的出题规律与题型分布
编程题一般 2~4 道,从易到难。第一题通常是简单数学计算或输入输出,中间题以字符串处理和计数统计为主,压轴题多半是模拟或枚举。数据范围不大,n ≤ 1000 到 n ≤ 10000 很常见,这意味着不需要学高深算法,O(n^2) 就能覆盖大部分题目。
四条出题规律值得记牢。模拟题给一个规则让你一步步执行,比如按天数累加、按指令移动、按条件筛选,重点考察读懂规则并翻译成循环。枚举题考暴力遍历加判断,要点是控制遍历范围和剪枝。排序题考自定义排序规则,比如按成绩排、按字典序排,往往要配合结构体。字符串题考统计、替换、回文判断,绕不开 getline 和 find。
| 题型 | 典型题面 | 常见数据范围 | 常用解法 |
|---|---|---|---|
| 简单计算 | 求和、判断奇偶、算平均数 | n ≤ 1000 | 顺序结构 + 循环 |
| 字符串处理 | 统计字符、判断回文 | 串长 ≤ 1000 | string + 循环 |
| 模拟 | 按规则执行流程 | n ≤ 10000 | while/for 逐步模拟 |
| 枚举 | 找合法组合 | n ≤ 1000 | 双重循环 + 条件判断 |
判分按测试点给分,不是「代码能跑就给分」。样例通过只代表一个测试点通过,判分系统还会输入边界数据,比如最大值、最小值、重复值、空输入。这也是为什么第 4 章把自测当成正式步骤来写,备赛阶段就要把「样例过了」和「能得分」分开看待。
2.3 备赛环境:编译器、IDE 与本地调试的基本配置
初学者我建议先用图形化 IDE,比如 Dev-C++、Code::Blocks 这种装完就能用的;熟悉命令行之后再换 VS Code 配编译器。选哪个不重要,重要的是搞清楚三件事:用什么语言标准编译、能不能看到警告、出错信息定位到哪一行。
命令行编译参数最好能默写:
g++ main.cpp -o main -std=c++11 -O2 -Wall-std=c++11指定语言标准,防止用了新语法却不自知;-O2开优化,让本地表现接近评测机;-Wall打开所有常见警告。警告不是错误,但多数时候警告就是 bug 的线索,比如变量未初始化、比较时类型不一致。初赛新手最容易忽略-Wall,建议每次提交前都带这个参数编译一次。
编译报错看不懂也是常见卡点。看到undefined reference to 'main',说明没写 main 函数或函数名拼错;看到expected ';' before,说明上一行漏了分号;看到'xxx' was not declared,说明头文件没包含或变量名拼错。这些提示是英文,但关键词就那几个,混熟之后基本不用问人。
本地调试我习惯用 freopen 把输入输出重定向到文件,这样测试数据不用反复粘贴:
#include <cstdio> int main() { freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); // 从这里写代码 return 0; }这样一来,样例和自造数据都放在 input.txt 里,程序运行后直接检查 output.txt。但有一个关键点:如果题目要求的是标准输入输出,提交前必须把 freopen 两行注释或删掉,否则评测机读不到数据,直接运行错误。这个错误太常见,后面避坑章还会再提。
提示:每个程序写完都先用命令行编译一次,确认在没有 IDE 辅助的环境下也能通过,这是模拟评测机的最简单方式。
3. 把基础语法打成肌肉记忆:输入输出、循环数组与字符串
编程题能不能快速写对,不取决于会多少算法,而取决于基础语法是否形成肌肉记忆。考场时间紧,你不可能现场查头文件和函数名。这一章给出三个最容易被扣分的语法点,每个都附可直接套用的代码。
3.1 scanf/printf 与 cin/cout:输入输出选型与提速细节
cin/cout 写起来方便,但默认同步机制比 scanf/printf 慢不少,数据量大时容易超时。解决方法是加两行提速代码:
#include <iostream> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin >> n; // 后续照常使用 cin/cout return 0; }第一行关闭 C 与 C++ 标准流的同步,第二行让 cin 不再每次刷新输出缓冲区。加了这两行,cin/cout 在绝大多数场景下速度就够用了。要注意的是,混用 scanf/printf 和 cin/cout 时不要加这两行,否则可能出现数据读乱的问题。
如果你习惯 scanf/printf,格式符要记牢:int 用%d,long long 用%lld,double 用%lf,char 用%c。scanf 对空格和换行是自动跳过的,所以"%d%d"和"%d %d"效果一样,不用担心格式串里要不要写空格。但%c不会跳过空白字符,读字符前如果不处理换行符,会读到意外的 '\n'。
#include <cstdio> int main() { int a, b; scanf("%d%d", &a, &b); printf("%d\n", a + b); return 0; }这里%d前不用写&以外的任何东西,但变量前必须加&取地址,这是新手最常漏的符号。printf 的\n必须写清楚,评测按行比对输出,少一个换行在部分评测器里会直接判格式错误。
3.2 循环与数组边界:越界是初赛失分的头号原因
数组越界在本地经常不报错,因为越界访问的是相邻内存,值看起来「正常」,但评测机上的内存布局不同,结果就随机了。这种问题最难排查,玄学味道很重,我一般直接养成三个习惯:下标从 0 开始就写for (int i = 0; i < n; i++),下标从 1 开始就写for (int i = 1; i <= n; i++),永远不让下标等于数组大小。
int a[100]; // 合法下标范围是 0~99 for (int i = 0; i < 100; i++) { a[i] = i; // i 最大 99,不越界 }如果你不确定需要多大,就按数据范围上限再加 5:n ≤ 1000 就开 1005,n ≤ 100000 就开 100005。多开几个元素对内存毫无影响,却能省掉一整类 bug。二维数组同理,习惯写成a[105][105],别写成a[100][100]后还去访问第 100 行。
循环边界还有两个常见错误:一是用<写成<=导致多走一次;二是在循环体内修改循环变量导致死循环。死循环的表现不是报错,而是超时,这种题往往要查很久。想减少这类错误,多练「读程序写结果」的题,边界问题能形成条件反射。
3.3 字符串处理与最小模板集:getline 和常用头文件
字符串是初赛编程题的第二大素材。string 比 char 数组好用得多,几个关键方法就能应付大部分题:
#include <iostream> #include <string> #include <algorithm> using namespace std; int main() { string s = "hello"; cout << s.length() << endl; // 长度,不含结束符 cout << s.substr(1, 3) << endl; // 从位置 1 起取 3 个字符,结果是 "ell" reverse(s.begin(), s.end()); // 反转整个字符串 if (s.find("ll") != string::npos) { cout << "found" << endl; } return 0; }说明:length 返回字符个数但不含结束符;substr 的第一个参数是起始位置,第二个参数是长度;find 找不到时返回 string::npos,这个判断必须写,因为 npos 是很大的无符号数,直接和整数比较会踩坑。
读取带空格的整行要用 getline,但 getline 之前如果有cin >>输入,会先读到一个残留的换行符。这是极高频的坑,建议直接背住修正写法:
int n; string line; cin >> n; cin.ignore(); // 丢掉数字后面的换行符 getline(cin, line); // 此时才真正读到整行内容cin.ignore()默认丢弃一个字符,这里恰好把残留的 '\n' 丢掉。如果漏掉这行,line 会是空串,后续所有处理全部错位,而且本地跑还看不出明显异常。
模板方面,我会让学生维护一个最小「万能开头」,每次写题先敲出来:
#include <iostream> #include <cstdio> #include <string> #include <algorithm> #include <vector> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(0); // 从这儿开始写 return 0; }这个模板覆盖了初赛编程题八成场景,剩下的 map、set 和 sort 需要时再单独加头文件。养成「每个程序都以 return 0 结束」的习惯也很重要,虽然评测器不强制,但个别考试系统会按返回值判断程序是否正常结束。
4. 编程题实战:一道计数模拟题从读题到 AC 的完整流程
光看语法永远学不会写题。这一章用一道典型的计数模拟题走完整流程:读题、设计、编码、自测。题目是我为练习设计的,难度对标初赛第三题左右,建议先独立想一遍再往下看。
4.1 读题三步:数据范围、输入格式、输出格式
题目:给定 n 个整数,统计出现次数最多的整数并输出它的出现次数;如果有多个整数次数并列,输出其中最小的那个。第一行是 n,第二行是 n 个整数。n ≤ 100000,每个整数的绝对值 ≤ 10^9。
读题我固定做三步。第一步看数据范围:n 到 100000,说明算法至少要低于 O(n^2),十万的平方是百亿次运算,限时内跑不完。第二步看输入格式:先整体读入再处理,不需要边读边输出。第三步看输出格式:输出两个值,第一个是那个整数,第二个是出现次数,中间用空格分隔。
第二步要特别标记「并列取最小」这个条件,它直接决定算法选择。如果只求最大次数,排序后扫一遍即可;加了取最小,用 map 按 key 升序的性质会更自然,遍历时只遇到严格大于才更新,自然保留最小的那个。
4.2 完整代码:map 统计与排序统计两种解法
先看 map 版本,这是我推荐的写法:
#include <iostream> #include <map> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin >> n; map<long long, int> cnt; // key: 整数本身, value: 出现次数 for (int i = 0; i < n; i++) { long long x; cin >> x; cnt[x]++; // key 不存在时自动初始化为 0 再自增 } long long ans; int maxCnt = 0; for (auto it = cnt.begin(); it != cnt.end(); it++) { if (it->second > maxCnt) { // 严格大于,保证并列时取更小的 key maxCnt = it->second; ans = it->first; } } cout << ans << " " << maxCnt << endl; return 0; }逻辑说明:先读入 n,再循环读入每个数统计到 map 中;统计完成后遍历 map,因为 map 默认按键从小到大排列,遍历顺序天然升序,所以更新条件写成>而不是>=,遇到次数相同的更小 key 时不覆盖,最终 ans 就是满足条件的最小值。
参数说明:这里用 long long 而不是 int,是因为题里说绝对值可能到 10^9,单个值虽然不超 int,但统计的 key 类型要与读入一致,统一用 long long 免得出类型转换问题。cnt[x]++在 key 不存在时会自动把 value 初始化为 0 再自增,这是 map 的默认行为,省掉了手动判断。
如果比赛环境内存紧张,可以换排序版本,常数更小、不依赖 map 实现:
#include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin >> n; vector<long long> a(n); for (int i = 0; i < n; i++) cin >> a[i]; sort(a.begin(), a.end()); long long ans = a[0]; int best = 1, cur = 1; for (int i = 1; i < n; i++) { if (a[i] == a[i - 1]) { cur++; } else { cur = 1; } if (cur > best) { // 从小到大扫描,小的数先被记录 best = cur; ans = a[i]; } } cout << ans << " " << best << endl; return 0; }排序后相同数连续排列,扫描时统计当前连续段长度;更新条件同样是严格大于,因为扫描方向是从小到大,并列时不覆盖,答案仍是最小值。两种写法都是 O(n log n),十万数据量轻松通过。
4.3 自测与边界条件:样例过了不等于能得分
写完不等于 AC,你不自测,评测机就会替你测。我固定做四类测试。第一类是样例,确认基本流程正确。第二类是边界:n 等于 1 时,答案应该是唯一的那个数和次数 1;所有数都相同,map 只有一个键;所有数都不同,最大次数是 1,答案取最小的数。
第三类是极端数值:负数、0、10^9 都要覆盖。第四类是并列情况。我常用的几组测试数据如下:
| 输入 | 预期输出 | 验证点 |
|---|---|---|
| 1 / 5 | 5 1 | n=1 边界 |
| 5 / 3 3 3 3 3 | 3 5 | 全部相同 |
| 6 / 2 3 2 3 5 6 | 2 2 | 并列取最小 |
| 4 / -1 -1 2 2 | -1 2 | 负数与并列 |
把每组数据按题目格式存进 input.txt,用 freopen 重定向后运行,拿输出结果与预期逐字符对比。如果还想更稳,可以写一个双重循环的暴力版本,再生成随机小数据让两个程序同时跑比较输出,这个对拍习惯能揪出几乎所有逻辑错误。初赛阶段手测五组数据通常已经够用,重点是养成「样例之后还有自测」的意识。
5. 初赛翻车现场:五个高频踩坑点与排查思路
下面五个问题来自各类赛事初赛的高频失分点,按「现象 → 原因 → 解决」写,建议把这一章当检查清单用,每道题提交前逐条对照。血泪经验告诉你,初赛丢分从来不是败在不会,而是败在习惯。
5.1 编译失败:本地能跑,评测机却不认
现象:本地 IDE 编译运行都正常,一提交就报编译错误。原因:本地编译器版本较新,评测环境可能是旧编译器或严格模式。常见触发点有三个:用了 bits/stdc++.h 这种万能头文件,旧编译器不认;用了 C++11 之后的语法但没有设置编译标准;源码带中文注释,文件编码不是 UTF-8。
解决:把万能头文件换成标准头文件,比如 、 、 、 ;提交前用命令行g++ main.cpp -o main -std=c++11编译验证一次;注释统一用英文或纯 ASCII。最稳的办法是赛前用官方指定的编译环境跑一个空程序,确认环境和你本地一致。
5.2 输出格式不符:多一个空格少一个换行直接零分
现象:逻辑全对,测试点 0 分,判分结果写「格式错误」。原因:评测按行比对输出,多余空格、缺失换行、英文大小写不一致都会判错。典型错误是循环输出时每个数后面都跟空格,行尾多出一个;或者两个输出值之间用了中文逗号。
解决:把输出重定向到文件,和样例输出逐字符对比,或用文本编辑器开启显示空格和行尾符号。写循环输出时用「先输第一个,后面每个前面加空格」的写法:
for (int i = 0; i < n; i++) { if (i) cout << " "; cout << a[i]; } cout << endl;if (i)在 i 为 0 时不输出空格,从第二个元素开始每个前面补一个空格,这样行尾干干净净,也省去判断「是不是最后一个」的复杂逻辑。另外 endl 会刷新缓冲区,循环里大量使用会比"\n"慢,建议用'\n'。
5.3 int 溢出:数据范围里的隐形炸弹
现象:小样例全对,大数据结果变成负数或明显不对。原因:int 最大约 21.4 亿,n 个整数求和或相乘很容易超界。初赛统计题常让算总和、平均数或乘积,n=100000、每个数 10^5 时总和就是 10^10,int 根本装不下。
解决:看数据范围时就判断结果数量级,参与累加、乘法的变量一律声明 long long,读入也用 long long。如果不确定,直接全用 long long,代价只是多占一点内存。两个 int 相乘前先转类型,写成1LL * a * b,避免 a*b 先用 int 算完再转导致的溢出。这类错误本地小数据根本测不出来,只能靠预先判断数据范围来防。
5.4 运行超时:暴力枚举撞上大测试点
现象:部分测试点通过,剩下的显示超时。原因:算法复杂度太高,n=100000 时用了两层循环 O(n^2),运算量过亿。初赛题目时限一般 1~3 秒,评测机每秒大约只能跑 10^8 次简单运算。
解决:写题前先按数据范围估算复杂度,n ≤ 10^5 就别用两层循环,改成 map、排序或前缀和处理。如果超时只发生在特定大测试点,优先改算法而不是调编译参数。还有一个隐蔽原因:死循环。检查循环变量有没有在循环体内被意外修改,或者 while 的退出条件是否可能永远不满足。
5.5 输入读取错位:getline 残留与文件读写搞混
现象:样例是数字加字符串,读出来的字符串是空的,后续结果全部错位。原因:cin >> n只读走数字,换行符留在缓冲区,紧接的 getline 把空串读走了。另一个方向是题目要求文件输入输出,你却只写了标准输入输出,评测读不到任何数据。
解决:数字后接整行字符串,先cin.ignore()再 getline;或者数字之后的所有输入都用 getline 读进来,再用 stringstream 拆分。文件读写按题目说明处理,凡是出现「从 xxx.in 读入、输出到 xxx.out」这类语句,就写:
freopen("xxx.in", "r", stdin); freopen("xxx.out", "w", stdout);提交时确认这两行保留;反之,题目是标准输入输出时这两行必须删掉。我习惯在写完代码后统一检查一次 freopen 的去留,把它当成和检查数组大小一样的固定动作。
注意:如果本地调试用 freopen 读了 input.txt,交上去前删掉这两行但忘了删对应代码,程序会退回标准输入,这时评测机能正常读到数据,但你自己测试时又会觉得「怎么不对了」。所以建议在代码里加一行注释标记 freopen 的开关状态。
6. 考前一周的冲刺清单:把「不犯错」练成习惯
最后一周不学新算法,只做三件事:背模板、手写题、走检查清单。没有后悔药,所以我把优先级和时间分配写清楚。
6.1 冲刺时间分配与必背模板
时间分配建议分成三段。每天 30 分钟背模板:输入输出提速、数组循环边界、getline 修正、排序、map 统计,每个模板手写一遍,不依赖 IDE 自动补全。每天 60 分钟写 1~2 道模拟题或字符串题,按第 4 章的流程走:读题三步、写代码、自测五组数据。每天 15 分钟过客观题错题,只看之前做错的读程序题,确认不再犯同类判断错误。
| 必背模板 | 对应知识点 | 熟练标准 |
|---|---|---|
| ios::sync_with_stdio(false) + cin.tie(0) | 输入输出提速 | 默写无误 |
| for + continue/break | 循环控制 | 能手推出执行次数 |
| cin.ignore() + getline | 字符串整行读取 | 知道为什么丢换行 |
| if (i) 空格控制输出 | 输出格式 | 行尾无多余空格 |
| map 计数 + 严格大于更新 | 统计取最值 | 能讲清并列逻辑 |
考场时间分配:客观题不超过 30 分钟,遇到卡壳的先跳过;编程题先写第一题和第二题,确保基础分到手,第三题如果 20 分钟还没有完整思路,回头检查前两题的输出格式和变量类型,把该拿的分拿稳。
我印象最深的一次,学生模拟题思路全对,只因为在输出末尾多敲了一个空格,一个测试点都没保住。那之后我要求他每次输出都重定向到文件,用 diff 和样例输出比对,一个字符一个字符地对。这个习惯看似笨,却救了他后面好几场。希望这篇笔记帮到你,也祝你在初赛里把「稳」字写到位。
本文还有配套的精品资源,点击获取