竞赛代码模板实战指南:从快读优化到ACM模式切换
发布时间:2026/9/30 10:03:20来源:尧图网络
简介面对 OI、ACM、PAT、CSP 等算法竞赛的高强度限时环境一套经过验证的代码模板能显著提升编码效率这套资源正是为参赛选手与刷题者准备的常用模板合集覆盖数据结构、排序搜索、动态规划、贪心回溯、数学数论、字符串匹配、图论网络流、计算几何等高频考点。模板多以清晰注释的 C 代码呈现并配有 Markdown 笔记便于读者理解原理并快速套用。压缩包共 53 个文件以 41 份 Markdown 思路笔记为主另有 11 个可直接运行的 C 示例和 1 个 .gitignore 配置文件整体仅 51KB轻量易用。目前已有 746 人学习/下载适合赛前梳理模板体系、查漏补缺的中级及以上竞赛选手按模块组织、目录清晰既可用于日常刷题对照也能在赛时快速定位所需模板是一份实用性强、覆盖全面的备赛工具包。1. 竞赛代码模板不是抄板子是给判题机对口型竞赛代码模板说白了就是在 OI、OJ、ACM、PAT、CSP 这类判题环境里反复用到的一套代码骨架。它不是拿来照抄的板子而是你上考场前就该练熟的口型读入是什么格式、评测机调的是 main 还是调用你写的函数、边界设多大、用什么编译指令。新手最普遍的误区是把模板理解成现场翻笔记结果每次都要重新调一遍读入、试一遍边界时间全耗在接口翻译上。真正有效率的做法是把它固定成肌肉记忆——拿到题先归类这题套模板里的哪一块剩余时间都花在推导上。下面不塞给你一份几百页模板库而是把我在 OI、ACM、PAT、CSP 和常见 OJ 刷题与机试准备里反复验证过的核心套路拆开讲板子怎么写、参数怎么调、哪些坑最容易让你翻车。2. 模板地基输入输出加速与快读快写的参数细节先说结论判题环境里cin/cout把同步关掉之后的表现足够应付大多数题目。剩下那部分卡 IO 的题基本出现在 OI 的大数据点和部分老 OJ 上——比如杭电 OJ 的图论题读十万行边表不做 IO 优化就会输在起跑线。模板第一层解决的就是“数据进得来、结果出得去”万能头文件、快读、以及main开头两行加速。这三个东西各有自己的坑下面拆开讲。2.1 bits/stdc.h 能用但提交前要确认三件事很多 OI 选手和 ACM 入门教程第一个教的就是#include bits/stdc.h它把标准库里能引的头一次性拉进来比赛时省去翻头文件的时间。在 GNU 系编译器下它真实存在NOI Linux、多数 OJ 的 G 环境都能过。但它不是标准 C 头换到 Clang、MSVC或者某些采用严格标准检查的评测机直接编译失败。我见过 PAT 和 CSP 模拟平台上有人因为bits/stdc.h吃 CE也见过校内 OJ 的老编译器把它当成普通头文件去搜目录然后报错。// 不要只依赖 bits/stdc.h备一份手动头文件列表 // OI / ACM / PAT / CSP 常见的 G 环境下这段足够用 #include cstdio #include cstring #include cstdlib #include algorithm #include cmath #include iostream #include vector #include queue #include stack #include map #include set #include string #include sstream using namespace std;这套手动头文件的好处是兼容性宽从郑州轻工业大学 OJ 到东方博宜 OJ 这类校内平台都能直接过旧标准。bits/stdc.h的另一个副作用是拖慢编译大数据题影响不大但本地反复调试时编译时间累积起来很磨人。建议你自己写代码时用手动头文件只在比赛环境明确支持时切换成万能头不要在模板里写死一个。2.2 手写快读判题机读十万个数时的生存技能快读的原理绕开cin和scanf的流解析直接用getchar逐字符拼数字。字符到数字的转换是纯内存操作省掉格式解析和类型检查所以能快不少。下面这段是我在 ACM 模式和 CSP 真题里常用的整型快读负数和多组数据都能处理。int read() { int x 0, f 1; char c getchar(); while (c 0 || c 9) { if (c -) f -1; // 处理负数记录符号 c getchar(); } while (c 0 c 9) { x x * 10 (c - 0); // 逐位累加 c getchar(); } return x * f; }调用时直接int n read();。注意第一个循环会跳过所有非数字字符所以读入文件里混着空格、换行、制表符都不影响。第二个循环遇到第一个非数字字符就停正好卡在下一个空格或换行前面。这个函数读不了long long如果题目数据范围超过int把x改成long long、返回值也改成long long就行。极端情况下比如 OI 的巨量输入getchar版本还不够需要用fread一次读一大块进缓冲区再逐个解析那是性能极致方案普通机试和大部分 OJ 题用不到。2.3 ios::sync_with_stdio(false) 开启后的三个后悔药cin/cout慢的根源是它要和 C 标准库 IO 做同步保证交替使用cin和scanf时数据不混乱。在main开头写两行能把这个包袱卸掉int main() { ios::sync_with_stdio(false); cin.tie(nullptr); // ... 正常写代码 }这里有两个参数容易害人。第一sync_with_stdio(false)一旦开启同一条代码里就不要混用printf和cin也不要混用scanf和cout否则缓冲区不同步输出顺序和内容都会变得随机这是经典的玄学报错看起来像数据错乱实际是 IO 打架。第二cin.tie(nullptr)解除的是cin与cout的绑定关系。默认情况下cin每次准备读入前会先刷新一次输出缓冲区这句话取消了cin和cout的绑定所以使用cin和cout时请一律用\n而不是endl结尾及时flush即可。第三个坑是关闭同步后scanf的格式化读入就废了。比如 PAT 乙级里经常要求一行读入三个带特殊分隔符的数据有些人习惯用scanf(Case #%d: %d %d, ...)这种格式化写法。用了sync_with_stdio(false)之后再写这种代码就是给自己埋雷。如果确定要用printf/scanf那就放弃这两行加速用std::ios自带的格式解析也能处理很多需求。我一般的原则是一个程序里只认准一种 IO 方式要么全cin/cout要么全scanf/printf绝对不混着用。3. 必背算法模板二分边界、最短路与快速幂的落地参数算法模板不是越多越好而是要在“写得快”和“写得对”之间取平衡。我在 ACM 入门阶段抄过一整本模板最后发现考场上真正反复用的就那几类二分、最短路、快速幂、并查集、线段树再加上字符串哈希。这一章挑三个最容易写错参数的展开每一个都把边界和溢出讲到能直接复现的程度。3.1 二分查找的闭区间与左闭右开把 lower_bound 写稳二分的坑不在思路在边界。while(l r)还是while(l r)r mid还是r mid - 1每次写都要重新较劲说明你还没有固定一套写法。我建议把“找第一个大于等于 target 的位置”这个场景固定成模板其余变体都从它推。// 标准 lower_bound返回第一个 target 的位置不存在时返回 n int lower_bound_pos(vectorint a, int target) { int n (int)a.size(); int l 0, r n; // 左闭右开区间 [l, r) while (l r) { int mid l (r - l) / 2; // 用减法代替加法防止 lr 溢出 if (a[mid] target) { r mid; // 左边还可能有答案保留 mid } else { l mid 1; // mid 太小直接排除 } } return l; }关键参数是mid的计算l (r - l) / 2而不是(l r) / 2因为后者在l和r都是大正数时可能溢出int。另一个关键是左闭右开区间里r初始等于n这样“找不到目标”时返回的l自然落在数组末尾不需要额外判断。如果想把题目改成“找第一个大于 target 的位置”把换成就完了想改成“找最后一个等于 target 的位置”用这个模板拿到下界后往前推一个判断即可。STL里自带lower_bound但手写模板的意义在于应对代码被禁用 STL 的老 OJ以及在面试手撕时不被边界问题卡住。3.2 堆优化 Dijkstra邻接表、优先队列和 INF 的配对关系最短路的模板几乎每场比赛都会碰到堆优化 Dijkstra 是稠密图和稀疏图里最稳定的选择。写这个模板时最值得注意的不是优先队列的用法而是INF这个常量的选择和long long的距离数组。const long long INF (1LL 62); vectorpairint, long long g[100005]; // 邻接表终点, 边权 long long dist[100005]; bool vis[100005]; void dijkstra(int s, int n) { for (int i 1; i n; i) { dist[i] INF; vis[i] false; } priority_queuepairlong long, int, vectorpairlong long, int, greaterpairlong long, int pq; dist[s] 0; pq.push({0LL, s}); while (!pq.empty()) { pairlong long, int top pq.top(); pq.pop(); long long d top.first; int u top.second; if (vis[u]) continue; // 每个点只在第一次出队时展开 vis[u] true; for (auto e : g[u]) { int v e.first; long long w e.second; if (dist[v] dist[u] w) { dist[v] dist[u] w; pq.push({dist[v], v}); } } } }INF用1LL 62是有讲究的它足够大能覆盖题目里最坏十万个点、十条边的累加和又不会大到加一个边权就溢出long long。vis数组负责跳过已经确定最短路的节点防止无效更新。优先队列里存的是pairlong long, int第一维距离第二维节点堆顶永远弹出当前最小的(dist, v)。如果你看的模板把pair写成pairint, int在边权较大时一定换成long long否则十个点全挂这是 ACM、CSP 真题里血泪教训最多的点。3.3 快速幂模板mod 为零、1LL、溢出三件套快速幂本身不难难在细节。下面是竞赛常用版long long qpow(long long a, long long b, long long mod) { long long res 1 % mod; // mod 可能为 0返回 0 而不是 1 while (b) { if (b 1) { res res * a % mod; } a a * a % mod; b 1; } return res; }res 1 % mod是第一个防呆点当mod是 1 时任何数对 1 取模都是 0写成res 1会返回错误答案。第二个防呆点在a * a当a和mod都接近long long上限时a * a会溢出正确做法是先用__int128承接中间结果或者在乘法前判断a mod先取模。第三个点是必须在返回值上统一类型底数、指数、模数里只要有一个超过int范围全部改成long long。这个模板在矩阵快速幂里同样适用把res换成单位矩阵、a换成矩阵、乘法换成矩阵乘法即可参数检查的逻辑不变。4. ACM 模式与核心代码模式在不同 OJ、PAT、CSP 之间切换套路做题平台多了以后会碰到一个很尴尬的问题在 LeetCode 上刷题从来不用管输入输出换到杭电 OJ 上就得自己写全套 main。这两年刷题圈子里对“ACM 模式”和“核心代码模式”讨论得很多华为 OD 机试也明确按 ACM 模式操作不少人在笔试时翻了车就是因为只练过一种模式。这一章专门说模式切换。4.1 完整程序 vs 核心函数OJ 刷题和华为 OD 机试的输入差异传统 OJ 是“完整程序模式”你提交的是带main的整个文件评测机把测试数据喂给 stdin它从 stdout 读结果。而核心代码模式是评测机已经帮你写好了输入直接调用你实现的函数常见于 PAT 的部分题目、CSP 认证的部分模拟平台以及 LeetCode 风格笔试题。ACMer 习惯的写法是前者。// ACM 模式完整程序自己在 main 里做 I/O // 典型场景杭电 OJ、传统 OJ、华为 OD 机试 #include iostream #include vector using namespace std; int main() { int n; while (cin n) { // 多组数据读到 EOF 结束 vectorint a(n); for (int i 0; i n; i) cin a[i]; long long sum 0; for (int v : a) sum v; cout sum \n; // 每组结果单独一行 } return 0; }// 核心代码模式无需写 I/O评测机把参数传进来 class Solution { public: int solve(vectorint nums) { long long sum 0; for (int v : nums) sum v; return (int)sum; } };两种模式的切换要点核心代码模式不用处理多组输入和 EOF但必须严格按题目给的函数签名来写返回值类型、参数传引用还是传值都不能改ACM 模式则要注意多组输入杀到 EOF漏写while(cin n)就会只跑第一组样例。华为 OD 机试的输入格式往往和传统 OJ 不完全一致常见的是第一行给几个数后面跟若干行中间用空格和逗号混着分隔。写代码前先把输入读进来打印一遍确认读入逻辑正确再往下做。4.2 PAT 乙级常见的读入套路EOF、行尾和格式化输出PAT 乙级题目对输出的格式化要求极其严格多打一个空格都是 WA。读入端也有它独特的习惯很多题目以“多组输入读到 EOF”作为前提而不是先给一个测试样例数。还有一类题是“第一行给 N后面 N 行每行一个操作”这时候一定要防住换行符残留。int n; cin n; // 先读整数 string line; getline(cin, line); // 这一步会吞掉换行符不是目标数据 getline(cin, line); // 真正的第一行数据很多人在 PAT 上第一次翻车就是因为这个空行。cin n只读数字后面的换行符留在输入流里第一次getline读到的必然是空串。解决方式要么在数字后额外调一次getline吃掉换行要么用cin.ignore(numeric_limitsstreamsize::max(), \n)。PAT 的题目还有一个特点是输出末尾常要求不能有多余空格我会在最后一段模板里留一个first标志变量第一个元素前不打空格之后的元素前打空格这样能绕开绝大多数格式化问题。4.3 CSP 字符串题的 getline stringstream 组合拳CSP 认证的很多题目会给一大段多行字符串要求按行解析再用空格或逗号切分成 token 序列。直接cin s只能读空格分隔的单词遇到“一行里可能包含多个空格和多个 tab”的情况就歇菜。我常用的组合是getline读整行stringstream做切分。#include sstream vectorstring split_line() { string line; getline(cin, line); // 一次读整行保留内部空格 stringstream ss(line); vectorstring tokens; string token; while (ss token) { // 按空白切分方便又稳 tokens.push_back(token); } return tokens; }stringstream会把连续空格、制表符都按一个分隔符处理不需要手动split。如果题目要求用逗号分隔先手写一个循环替换逗号为空格再继续走stringstream。比写复杂的状态机快得多。CSP 题目另一特点是数据规模给得实在不会故意刁难 IO但读入方式错了会在整行解析时把后续所有数据搞乱所以我在每场模拟赛前都先把这段split_line在编辑器里敲一遍保证没有语法层面的生疏。5. 模板翻车排查五个高频现场和它们的仪表盘信号模板不是背下来就能稳过真正的问题永远发生在各种平台细节里。下面是五条覆盖“本地编译、样例通过、大数据全挂、递归爆栈、类型溢出”的踩坑记录每条按现象、原因、解决三个步骤写方便你对着排查。5.1 本地编译通过提交 CE现象本地 G 编译一切正常提交到 OJ 或 PAT 报编译错误。原因有三类最常见的是评测机编译器版本较旧比如本地用的 C17 写auto [a, b] pair_val结构化绑定评判端只支持 C11直接语法错误第二类是你用了bits/stdc.h碰到 GCC 版本过老或编译器非 GCC 的环境会找不到这个头第三类是某些校内 OJ 使用 VC 编译器不支持%d之外的某些格式化写法。解决提交前查 OJ 首页支持的编译选项优先用 C11/14 的保守语法避免依赖 C17 特性同时把万能头替换为手动头文件列表。如果编译错误信息看一眼是头文件问题立刻换手动 include 重交一次。5.2 样例全对换大数据 TLE现象样例跑得快如飞交上去 Time Limit Exceeded。原因通常不是单点的常数慢而是复杂度算错了。比如O(n^2)的暴力过了样例里的小 n换到 CSP 认证的 10^5 量级就直接卧倒。解决思路是回到题目规模做一次心算10^7是 1 秒上下10^8是 2 秒之上的极限10^9必挂。如果算出来自己的代码在10^7以上要么从暴力改成二分、哈希、并查集等优化结构要么做剪枝。换句话说TLE 不是优化出来的是设计阶段就该算出来的。5.3 递归爆栈现象递归深度上万层本地偶尔过OJ 上跑着跑着 Segmentation Fault 或者 Runtime Error。原因评测环境的栈空间有限OI、OJ 的递归栈一般也就几 MB深度超过10^5就会爆。比如深度优先遍历一条链状的图递归函数一层层压栈每个栈帧还带着局部变量直接顶穿。解决把递归写成显式栈循环或者用vector模拟栈的入出。另一个临时方案是加大编译器的栈大小比如 G 加-Wl,-stack,size但提交时大多数 OJ 不让你改编译参数所以把核心函数从递归改成迭代才是一劳永逸的。5.4 int 乘法溢出现象小数据全对数据一大输出就开始随机飘负数或者错误大数。原因是题目里两个数的乘积超过int上限2^31-1。典型的坑发生在“求两点之间的距离平方和”这类题坐标是10^5级别平方后就是10^10int存不下。解决所有可能参与乘法的中间量都提升成long long乘法时在第一个量后加1LL比如1LL * a * b强制走long long运算。另外INF常量也要用long long版本不能在long long距离数组里填0x3f3f3f3f——那只配 int 数组使用。5.5 getline 接在 cinn 后面读到空行现象写了cin n后接getline(cin, s)读到的 s 是空字符串后续所有逻辑全部错位。原因是运算符会在目标变量填充后停下来但换行符还留在输入流里getline默认读到换行符为止于是吃到的第一样东西就是换行符本身。解决在和getline之间加一行cin.ignore(numeric_limitsstreamsize::max(), \n)把缓冲区里的换行清干净。这个坑在 PAT 和 CSP 的字符串题里几乎必出现建议直接写进你的核心模板注释里不要到考场上再来回忆。6. 模板到肌肉记忆对拍脚本与模板目录组织最后聊一个常被忽略的验证方法对拍。很多人的“模板练习”就是反复抄抄到自己信手写出来就算完。真正有效的是拿随机小数据和暴力写法对拍用脚本批量验证模板的正确性。我自己会在比赛前把模板目录按功能拆开每次只动一根手指。# 对拍脚本 stress.sh随机数据验证 sol.cpp 与 brute.cpp 的输出 for i in $(seq 1 200); do ./data in.txt # 数据生成器输出到 in.txt ./brute in.txt out_brute.txt ./sol in.txt out_sol.txt if ! diff -q out_brute.txt out_sol.txt /dev/null; then echo 第 $i 组数据不一致 break fi done这里data.cpp是随机数据生成器brute.cpp是确保正确的暴力实现sol.cpp是你要测试的带模板代码。用法上先编译三个可执行文件再跑脚本。它对二分、最短路、字符串哈希这类正确性敏感的模板特别有用一次对拍能筛掉你在边界上漏掉的百分之八十问题。模板目录我一般按00_head、01_io、02_math、03_graph、04_string这样组织每个文件只放一个独立函数避免“全合在一起改一处牵连别处”的窘境。刷题这件事我最大的体会就是不要高估临场发挥也不要低估肌肉记忆。每次模拟赛前我把最常用的四五个模板盲打一遍打到不会卡壳再把踩过的坑当成 acm 日记记下来。模板管理本质上是风险管理把代码里最容易出错的那几步拆出来反复练练到条件反射。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网