
简介这份PDF资料为《信息学奥赛一本通·编程启蒙 C版》面向零基础中小学生及CSP-J初赛入门选手用于解决编程启蒙阶段缺教材、缺练习主线的问题。全书共189页以课程式目录推进从第一个C程序、cout输出与cin输入讲起依次覆盖int整型、char字符类型、浮点类型、赋值与自增自减等特殊运算符、数据类型范围与转换再到printf/scanf函数、四舍六入五留双、if与if-else、三目运算符、分支嵌套、逻辑运算符、switch语句以及for、while、do-while循环等控制结构并按课配套大量练习题与博文链接便于边学边练。资源包为1个pdf文件大小约1.06MB单文件结构轻便可直接在电脑或平板上阅读检索。目前已有3069人学习下载适合刚接触C、准备信息学奥赛入门或需要系统梳理语法基础的读者作为课程序列化自学材料。1. 从《信息学奥赛一本通·编程启蒙 C版》说起启蒙阶段到底该练什么翻开《信息学奥赛一本通·编程启蒙 C版》的人大多不是零基础的小孩而是带竞赛班的信息技术老师或者从 Python 转过来想啃算法的成年人。前者关心怎么按章节排课、怎么让机房四十台机器都跑出 Hello World后者关心例题能不能自己敲出来、题解看不看得懂、卡住时错在边界还是错在思路上。这本书处理的是「语法到题目」之间的过渡变量、分支、循环、数组、函数、排序、查找、简单图论被铺成一条线每个知识点后面紧跟可提交的练习。真正决定启蒙效果的不是书本身而是书里例题能否在本地稳定复现的那套环境、每道题边界条件想清楚的习惯以及能自查错在哪的调试方法。下面按环境、语法、算法、自查的顺序把这条线拆开。2. 用 VS Code 把一本通例题跑起来编译器、tasks.json 与 cin/cout一本通的前几章几乎全是控制台程序输入一行、输出一行看起来简单但新手最常卡住的地方恰恰是「代码写完按哪个键」。VS Code 本身只是个编辑器它不带编译器也没法直接跑 C所以第一件事是把工具链补齐再把编辑器接上去。2.1 VS Code 配置 C/C 环境编译器、扩展与工作区选编译器这件事各平台差别不小但目标是一致的让终端里敲g --version能打印出东西。Windows 上最常见的是 MinGW-w64 或 MSYS2 提供的 gmacOS 装完 Xcode Command Line Tools 就有clang语法兼容度足够跑一本通的例题Linux 装build-essential即可。平台推荐编译器安装方式常见坑WindowsMinGW-w64 gMSYS2 或独立发行包装完把bin加进 PATHPATH 没生效终端里g报找不到命令macOSApple clangxcode-select --install首次调用会弹权限确认框Linuxgapt install build-essential/dnf groupinstall Development Tools版本偏旧编译时要补-stdc17VS Code 里需要装的是 Microsoft 官方的 C/C 扩展装完才有补全和跳转。工作区里会自动生成.vscode/c_cpp_properties.json其中compilerPath要指向真实的编译器路径includePath一般填${workspaceFolder}/**就够了。如果补全一直不生效多半是compilerPath指向了不存在的文件或者扩展把 Windows 上的 MSVC 当默认编译器而机器上只装了 MinGW。注意在 Windows 上搜到error: microsoft visual c 14.0 or greater is required时那是 Python 在编译 C 扩展时找不到 MSVC 构建工具和用一本通写 C 程序不是同一件事别照着那个报错去装一堆无关组件。2.2 最小可运行的编译命令与 tasks.json先确认命令行能跑通再交给编辑器。把下面这段存成a.cpp在终端里执行两条命令# 编译-stdc17 指定标准-O2 开优化-Wall 打开常用警告 g -stdc17 -O2 -Wall a.cpp -o a # 运行Windows 下可执行文件是 a.exeLinux/macOS 下是 ./a ./a命令行跑通之后再把同样的编译参数固化到.vscode/tasks.json按CtrlShiftB就能一键编译当前文件{ version: 2.0.0, tasks: [ { label: g build active file, type: shell, command: g, args: [ -g, // 保留调试信息供 gdb 断点使用 -stdc17, // 章节里的 auto、结构化绑定需要它 -O2, // 优化避免本地通过、提交超时 -Wall, // 打开警告未初始化变量会提示 ${file}, // 当前打开的源文件 -o, ${fileDirname}/${fileBasenameNoExtension} ], group: { kind: build, isDefault: true } } ] }参数里最容易忽略的是-Wall。一本通里不少题目的坑就是变量没赋初值、数组越界、把写成这些在有警告的情况下编译器会直接指出来比提交后对着 WA 发呆快得多。-O2也不能省尤其排序和循环题本地不加优化的运行时间往往比评测机慢好几倍。2.3 输入输出一本通例题为什么总用 cin/cout一本通的例题和习题基本都用cin/cout原因是它们类型安全读整数、字符、字符串不需要在格式串里对类型写错了编译器直接报错而scanf里%d配long long只会给你一个莫名其妙的答案。代价是默认状态下cin比scanf慢遇到十万级以上的数据量会超时所以习惯上加一行加速#include bits/stdc.h using namespace std; int main() { // 关闭 C 与 C 流同步、解除 cin 与 cout 绑定可显著提速 ios::sync_with_stdio(false); cin.tie(nullptr); int a, b; // 读到文件末尾自动结束对应一本通里多组数据的题型 while (cin a b) { cout a b endl; } return 0; }while (cin a b)这种写法对应的是「输入包含多组测试数据」的题面用cin的返回值判断是否读完比手动猜数据组数稳妥。加了sync_with_stdio(false)之后不要混用scanf和cin两者的缓冲区不再同步输出顺序可能错乱cin.tie(nullptr)的作用是解绑cout避免每次读入前强制刷新输出缓冲。真正需要极致速度时endl也该换成\n因为endl每次都刷新缓冲区。3. 一本通编程启蒙的语法骨架变量、分支循环、数组与字符串初始化语法部分在一本通里占了大半篇幅看起来零散其实可以归成三类数据怎么存、流程怎么走、批量数据怎么组织。这三类里各有一个高频翻车点早搞清楚能省下大量调试时间。3.1 变量与整型溢出为什么一本通爱用 long long启蒙阶段最常见的事故是溢出题目要求算 1 到 100 的和用int没问题要求算阶乘或者累加一百万个数就得换long long。判断标准很简单先估算结果量级再对照类型范围挑一个有余量的。类型典型宽度大致范围一本通里的常见用途int32 位±2.1×10循环下标、计数器long long64 位±9.2×10¹⁸求和、阶乘、大整数乘法结果double64 位15~16 位有效数字平均值、几何计算char8 位-128~127单个字符、字符数组元素long long sum 0; // 累加器优先用 long long防止中途溢出 for (int i 1; i n; i) { sum i; // n 到 1e5 量级时 int 就已经不够用了 } cout sum \n;这段代码里sum声明成long longi仍然是int因为下标不会超过 32 位范围。常见误用是等号两边类型不一致比如int total a * b;中a、b都是int乘法先按 32 位算完再赋值结果早就溢出截断了正确写法是先把其中一个转成long long或者干脆把参与运算的量都声明成long long。3.2 分支与循环从判断质数看边界处理判断质数是一本通里出镜率最高的练习之一它把分支、循环、边界三样东西揉在一道题里。朴素写法从 2 试除到 n-1能过但慢稍微优化到 √n性能立刻上一个台阶。关键在于边界小于 2 的数直接返回假偶数单独处理剩下的只试奇数。bool isPrime(long long n) { if (n 2) return false; // 0、1 和负数都不是质数 if (n % 2 0) return n 2; // 2 是唯一的偶质数 // 用 i n / i 代替 i * i n避免 i*i 在接近上限时溢出 for (long long i 3; i n / i; i 2) { if (n % i 0) return false; // 找到因子立刻返回 } return true; }i n / i和i * i n在数学上等价但前者对long long上限更安全。循环每次加 2 而不是加 1是因为偶数已经被前面的分支排除了试除次数直接减半。这类题目真正的考点不是算法本身而是有没有覆盖 n1、n2、n 为大质数这三种边界写完之后自己手动构造几组测试跑一遍比交给评测机猜要可靠。3.3 数组与字符串初始化字符串数组的三种写法一本通里字符串的用法分两类C 风格的char数组和 C 的std::string。前者是题目输入里%s对应的东西后者写起来省事能直接比较、拼接。初学阶段经常把两者混着用导致比较的其实是两个地址而不是内容。char s1[] hello; // 字符数组末尾自动补 \0长度为 6 string s2 hello; // std::string可直接用 比较内容 vectorstring vs(3); // 3 个空字符串 vectorstring vs2(3, x); // 3 个 x char grid[105][105] {}; // 二维字符数组全部初始化为 \0 string strs[10] {}; // 字符串数组全部初始化为空串char grid[105][105] {}这种写法在做地图类题目时很关键花括号里的空初始化会把整块内存清零否则数组里是随机字节判断grid[i][j] .时会莫名其妙为真。std::string数组的默认初始化则不需要担心它会调用构造函数生成空串。用vectorstring vs(3)时注意参数是元素个数而不是容量很容易和reserve记混。4. 一本通题解里的四类经典题判断质数、冒泡排序、二分查找与弗洛伊德算法教材后半段开始出现成体系的算法题其中四类出现频率最高也最容易被写错质数判断、冒泡排序、二分查找、弗洛伊德最短路。它们的共同特点是代码短、边界多看题解三分钟就懂自己写要调半小时。4.1 判断质数从试除到筛法的选择单点判断用上一节的试除法复杂度 O(√n)够用。但一本通里还有一类题要求「输出 1 到 n 之间的所有质数」这时逐个试除的复杂度会涨到 O(n√n)n 到一百万就危险了。常见做法是换埃氏筛开一个布尔数组先把所有数标为质数再从 2 开始把每个质数的倍数划掉。const int N 1000005; vectorbool isP(N, true); // 全部先标记为质数 void sieve(int n) { isP[0] isP[1] false; // 0 和 1 不是质数 for (int i 2; i n / i; i) { if (!isP[i]) continue; // 已经被更小的质数筛掉 // 从 i*i 开始划因为更小的倍数已被前面的质数处理过 for (int j i * i; j n; j i) isP[j] false; } }内层从i * i开始是关键优化i * 2、i * 3这些更小的倍数在遍历到 2、3 时已经划掉了重复划只是浪费时间。用vectorbool而不是bool[N]是因为前者按位存储一百万的数组只占约 125KB避免了全局数组开不下或者栈上爆栈的问题。4.2 冒泡排序一本通排序题的通用解法和它的天花板冒泡排序在一本通里的地位是「理解排序」而不是「解决排序」。它的两层循环结构直观便于讲清楚比较和交换但复杂度 O(n²)n 过万就会超时。写的时候有两个细节值得加内层循环的上界随外层递减以及加一个交换标记做提前退出。void bubbleSort(vectorint a) { int n a.size(); for (int i 0; i n - 1; i) { bool swapped false; // 每轮结束后末尾 i1 个元素已经就位不必再比 for (int j 0; j n - 1 - i; j) { if (a[j] a[j 1]) { swap(a[j], a[j 1]); swapped true; } } if (!swapped) break; // 本轮无交换说明已经有序 } }如果题目只要结果、不限制必须手写排序直接用sort(a.begin(), a.end())更快更稳需要引入algorithm。冒泡排序的额外价值在于它稳定相等元素不会互换位置遇到「按成绩排序、成绩相同按输入顺序」这类要求时稳定排序能省掉一个次级关键字。想清楚题目到底考算法还是考结果再决定手写还是调库。4.3 二分查找有序区间上的边界与死循环二分查找在一本通里通常以「在有序数组中查找某个数」或「求第一个大于等于 x 的位置」出现前者简单后者是各种「二分答案」题型的基础。手写时最大的坑是区间定义不统一左闭右开和左闭右闭的更新方式不同混用就会出现死循环或者漏掉最后一个元素。// 返回第一个 target 的下标区间为左闭右开 [lo, hi) int lowerBound(const vectorint a, int target) { int lo 0, hi a.size(); while (lo hi) { int mid lo (hi - lo) / 2; // 防 lohi 溢出 if (a[mid] target) lo mid 1; else hi mid; // mid 可能就是答案不能减一 } return lo; // 返回位置或 a.size() 表示不存在 }mid lo (hi - lo) / 2比(lo hi) / 2更安全虽然题目里下标很少真的溢出但养成习惯没坏处。hi mid而不是hi mid - 1是因为mid本身可能就是答案直接减一会把它排除掉。调试二分的通用办法是拿长度为 0、1、2 的数组各跑一遍再试目标值小于全部、等于首元素、等于末元素、大于全部这四种情况。4.4 弗洛伊德算法信息学奥赛一本通图论题的三重循环顺序弗洛伊德算法求多源最短路核心就三重循环加一句松弛但顺序写错就全错。外层必须枚举中转点 k内两层枚举起点 i 和终点 j含义是「只允许经过前 k 个点作为中转时i 到 j 的最短距离」。const int INF 0x3f3f3f3f; // 足够大且两个相加不会溢出 int int n, m; vectorvectorint d(n 1, vectorint(n 1, INF)); for (int i 1; i n; i) d[i][i] 0; // 自己到自己的距离 // ... 读入边 (u, v, w)重边取较小值 for (int k 1; k n; k) // 中转点必须在最外层 for (int i 1; i n; i) for (int j 1; j n; j) if (d[i][k] d[k][j] d[i][j]) d[i][j] d[i][k] d[k][j];把 k 放到最内层是初学者最常犯的错误那样相当于每对 (i, j) 只考虑了一个中转点得到的不是最短路径。选0x3f3f3f3f作为无穷大是因为两个它相加约等于 2.1×10⁹还在int范围内不会因为溢出变成负数而误判松弛条件。复杂度 O(n³)n 超过 500 就要考虑换成其他最短路算法一本通里对应的题目规模通常在 100 到 300 之间。5. 一本通刷题不掉队的三件工具断点调试、对拍脚本与错题回看节奏写完能过样例不代表能过评测。样例往往只有一两组边界、极端数据、多组输入这些情况全在样例之外。把「自查」这一步做扎实比多刷十道题更划算。5.1 断点调试比 printf 更快定位越界和错值在 VS Code 里按F5启动调试需要先配好launch.json关键是program指向刚编译出的可执行文件preLaunchTask填前面 tasks.json 里的标签名这样按 F5 会先自动编译。断点打在循环内部用「监视」窗口盯住下标和累加器能很快发现数组下标从 1 开始写、循环却从 0 开始这类问题。还有一种更土但更快的手段把中间结果输出到cerr而不是cout。cerr不带缓冲即使程序崩了也能看到最后打印的内容而且它和cout走不同流不会污染提交时的标准输出。定位完记得删掉这些调试语句。5.2 对拍脚本用暴力解验证你的优化解当一道题写出了优化解但不确定是否正确标准做法是再写一份必然正确的暴力解随机生成小数据让两份程序对拍。#!/bin/bash g -O2 -stdc17 brute.cpp -o brute # 暴力解逻辑简单但慢 g -O2 -stdc17 fast.cpp -o fast # 待验证的优化解 g -O2 -stdc17 gen.cpp -o gen # 随机数据生成器 for ((i 1; i 500; i)); do ./gen $i in.txt # 把 i 当随机种子保证可复现 ./brute in.txt out1.txt ./fast in.txt out2.txt if ! diff -q out1.txt out2.txt /dev/null; then echo 第 $i 组数据不一致; cat in.txt; break fi done生成器里用固定种子比如srand(atoi(argv[1]))这样复现问题时能直接拿种子重新生成同一组数据。数据规模要小n 取 5 到 10 就够规模一大暴力解跑不动对拍效率反而下降。跑出不一致的那组输入通常会直接暴露边界错误。5.3 刷题节奏与错题回看把 WA 变成清单启蒙阶段最容易犯的错是「一道题 WA 了就重写一遍AC 了就翻页」。更有效的做法是维护一份错因清单数组开小了、多组数据没重置、long long忘了开、二分边界加减一搞反、读入字符时前导空格没吃掉。每道 WA 的题往里加一条过两周回头看重复出现的错因基本就那么五六条。刷题节奏上一章的例题先自己敲一遍再看题解习题按「简单—中等—挑战」分三档当天只做前两档第三档留到第二天头脑清醒时再碰卡住超过四十分钟就去看思路而不是抄代码。把i n / i写对、把 k 放在最外层、把数组初始化成{}这些细碎动作才是从能跑通到能提交之间的那段距离。本文还有配套的精品资源点击获取