
简介这是一份面向ACM国际大学生程序设计竞赛及同类算法竞赛备赛者的个人整理编程模板将常考的素数筛、快速幂、大数运算、辗转相除等基础算法浓缩进一个PDF文档适合需要快速调用模板代码的参赛选手和算法学习者。压缩包内仅含1个PDF文件体积2.14MB轻巧便携已有301人学习下载。内容覆盖头文件标准组合、埃拉托斯特尼筛法求素数、快速幂取模、大数加法与大数阶乘模拟、最大公约数与最小公倍数求解等核心模块每段代码均配有调试通过的C实现和简明注释风格统一便于直接复制套用。通过这套模板读者既能系统回顾常用算法细节也能在比赛现场快速搭建代码基础将更多时间投入题目分析和策略设计切实提升编码速度和解题效率。1. 为什么每位ACM选手都需要一份自己的模板先说一个很多刚入门的朋友容易误解的事情ACM比赛允许带纸质资料入场所以模板是合法且必要的武器。但同样是带模板有人带的是自己在训练中一点点积累的“武器库”有人带的是从网上复制打印的“大部头”——两者的实战效果天差地别。我见过不少选手的做法是赛前从各路博客、学长代码、GitHub仓库里扒下来一堆现成模板拼成一份几百页的PDF打印出来厚厚一摞感觉自己“装备齐全”。结果一到比赛现场遇到稍微变形一点的题目这堆模板根本帮不上忙。原因很简单别人的模板是别人思考方式的产物代码风格、变量命名、边界处理习惯都和你不一样临时翻阅根本来不及理解更别说改造成适合当前题目的解法。我在校队带新人的时候经常说一句话模板整理的过程本质上是你对自己算法知识体系的一次全面梳理。你不是在“收集代码”你是在把学过的东西用自己的方式重新表达一遍。这份PDF的价值不在于它有多厚而在于它是否真正属于你——里面的每一段代码你都亲手敲过每一处边界条件你都踩过坑每一个注释你都看得懂为什么要写。这也是为什么这份PDF建议从大二开始就持续维护而不是等到区域赛前一周才突击整理。整个准备过程大概需要两到三个月的持续投入但收益会体现在你后续每一场比赛中。下面我把自己整理ACM模板的完整思路、结构和踩过的坑都写出来希望能帮你少走一些弯路。2. 模板整理的时间节点与准备工作2.1 什么时候开始整理最合适很多人的误区是“等我把算法学完了再整理”但实际上你永远不会觉得“学完了”。算法的学习曲线是无限延伸的今天觉得已经掌握的知识点明天遇到一道综合题就会发现理解还不够深入。正确的做法是当你学完一个算法并且做了一定数量的例题之后趁热打铁把它整理进模板。我个人推荐的时间线是这样的大一到大二上学期跟着集训队的训练节奏学基础算法每学完一个专题就顺手整理对应的模板部分。这个阶段的模板可能很粗糙但没关系先用起来。大二下学期到大三上学期这是模板的快速迭代期。经过省赛和几场网络赛的洗礼你会发现哪些模板是高频使用的哪些模板写得太复杂不实用逐步精简和优化。大三之后模板基本定型每次比赛后只做小幅修正。这时候重点已经不是“写模板”而是“用模板”——真正比赛时你能快速定位到需要的代码段并且能根据题目要求灵活修改。如果你已经大二甚至大三了还没开始整理也不用慌。我曾经见过一个学弟用暑假两周时间集中整理了完整模板因为他平时解题量足够大整理起来其实就是把以往写过的代码精选重构一遍。关键是平时要有积累而不是从零开始写。2.2 整理前需要准备的材料与工具动手整理之前先把你手头的素材盘一盘。最基础的材料包括你在OJ上提交并通过的代码这是最重要的素材来源因为它们经过实际检验正确性有保障。集训队内部讲义和课程笔记这些材料里有算法的推导过程和复杂度分析可以作为模板开头注释部分的素材。经典书籍和参考资料比如《算法竞赛入门经典》《挑战程序设计竞赛》用来对照和补充你模板中缺失的边界情况处理。工具方面我个人用的是VS Code写代码Typora做排版Git做版本管理。代码的整理顺序我会在下一节详细说这里先讲一个容易被忽略的问题统一代码风格。既然这份模板是你自己一个人用风格统一能让你在比赛中快速找到需要修改的位置。我的模板代码统一遵循以下规则使用//注释不用/* */这样单独删除某行注释时不会误删代码块变量名用有意义的英文缩写比如dis表示距离fa表示父节点核心代码和辅助代码之间用空行分隔方便快速定位每个模板开头标注算法名称和适用场景偶尔写出关键复杂度每行代码不超过80个字符避免打印出来换行错乱影响阅读。这些规则听起来琐碎但在赛场上一紧张你根本没时间把握代码细节模板的排版就是你的“肌肉记忆”——扫一眼就知道变量含义找到需要修改的地方。3. 模板目录结构按知识模块划分的实战布局模板PDF的目录结构直接决定了赛场上翻查的效率。一个好的目录不是简单地按字母排列算法名称而是按照“比赛中遇到问题时的思考路径”来组织。比赛时你拿到一道题第一反应通常是这是什么类型的题数据结构和图论往往是最先需要查的因为它们出镜率最高。我最终定稿的模板目录结构如下基础篇头文件、常用宏定义、输入输出优化快读、对拍脚本数据结构单调栈、单调队列、并查集、树状数组、线段树、ST表、莫队、树上启发式合并图论最短路Dijkstra、SPFA、Floyd、最小生成树Kruskal、Prim、拓扑排序、强连通分量、欧拉路、网络流Dinic、费用流、二分图匹配数论GCD与扩展欧几里得、中国剩余定理、素数筛法、快速幂、组合数取模、伯努利数、莫比乌斯反演字符串KMP、Trie树、AC自动机、后缀数组、后缀自动机、Manacher、字符串哈希计算几何基础精度处理、点线关系、凸包、旋转卡壳、半平面交动态规划背包九讲、区间DP、状态压缩DP、数位DP、斜率优化、单调队列优化DP这里有个重要的取舍原则贪多嚼不烂。我见过有些人的模板目录五十多个章节几乎把《算法导论》的目录抄了一遍。但仔细看内容很多章节只是抄了一段代码自己根本没理解。这种做法在比赛时不仅起不到帮助反而会因为翻找浪费时间。我的建议是最终定稿控制在25到35个核心模板之间确保每个模板都是你信手拈来的熟练工具而不是厚厚一沓你只求“需要的时候能找到”的陌生代码。每个模板的编写格式也建议统一。以线段树为例我的模板格式是// 线段树 - 区间最大字段和 // 适用单点修改区间查询且信息可合并 // 复杂度建树 O(n)查询/修改 O(log n) const int MAXN 1e5 5; int tree[MAXN 2]; void pushUp(int rt) { tree[rt] max(tree[rt 1], tree[rt 1 | 1]); } void build(int rt, int l, int r) { if (l r) { tree[rt] arr[l]; return; } int mid (l r) 1; build(rt 1, l, mid); build(rt 1 | 1, mid 1, r); pushUp(rt); } void update(int rt, int l, int r, int pos, int val) { if (l r) { tree[rt] val; return; } int mid (l r) 1; if (pos mid) update(rt 1, l, mid, pos, val); else update(rt 1 | 1, mid 1, r, pos, val); pushUp(rt); } int query(int rt, int l, int r, int L, int R) { if (L l r R) return tree[rt]; int mid (l r) 1; int res -INF; if (L mid) res max(res, query(rt 1, l, mid, L, R)); if (R mid) res max(res, query(rt 1 | 1, mid 1, r, L, R)); return res; }看到没有注释只写“适用范围”和“复杂度”不写冗长的算法讲解。比赛时你没时间读大段文字你需要的只是一个快速触发记忆的提示。算法细节如果记不清应该在准备阶段就去复习而不是寄希望于模板里塞一篇小论文。3.1 基础篇快读与常用宏定义是你最先需要的内容基础篇是整个模板里最不起眼但赛前检查时最先要看的部分。尤其是快读模板C的cin/cout即使关闭了同步在某些场合依然不够快scanf/printf在输入量巨大的题目里也可能卡时间。我的模板开头是这两段// 关闭同步后 cin/cout 可以替代 scanf/printf ios::sync_with_stdio(false); cin.tie(nullptr);以及手写快读inline int read() { int x 0, f 1; char ch getchar(); while (ch 0 || ch 9) { if (ch -) f -1; ch getchar(); } while (ch 0 ch 9) { x x * 10 ch - 0; ch getchar(); } return x * f; }这里有个小细节手写快读的inline关键字很重要它在编译时会尝试将函数体嵌入到调用处减少函数调用开销。另外如果比赛环境和评测机是新版GCC编译器用fread批量读入的效率会更高但代码复杂度也更高。我自己在实际比赛中发现getchar版本已经能应付大多数情况只有遇到极端的百万级输入才会切换到fread版本这个选择你可以根据自己的习惯来定。对拍脚本也是基础篇里容易被忽视但非常重要的部分。所谓对拍就是写一个数据生成器、一个暴力解、一个高效解然后随机生成数据不断对比暴力解和高效解的输出是否一致。这个流程在你怀疑自己的算法有隐蔽bug时几乎是唯一的调试手段。生成器的模板可以写成这样#include bits/stdc.h using namespace std; int main() { srand(time(nullptr)); int n rand() % 1000 1; printf(%d\n, n); for (int i 0; i n; i) { printf(%d , rand() % 100000); } return 0; }把生成器编译成gen暴力解编译成brute高效解编译成solve然后在终端里跑一个无限循环每次用gen生成测试数据分别喂给brute和solve遇到输出不一致就停下来检查。这个方法能帮你省下无数用来肉眼盯代码的时间。4. 模板编写中的代码风格与可复用性这一节可能是整篇文章里最“干货”的部分因为代码风格直接决定了赛场上你能多快地修改模板。很多新人喜欢把模板写成一个完整的、可以直接AC提交的程序这其实是个很大的误区。比赛时你遇到的题目几乎不可能和模板原题一样你需要做的是把模板里的核心函数抽出来嵌到你的新代码里。因此模板的核心单元应该是函数而不是完整代码。拿图论的最短路举例我的Dijkstra模板不是给出一整段能跑的main函数而是提炼出可以直接调用的函数typedef pairint, int pii; void dijkstra(int s, vectorint dist, vectorvectorpii graph) { priority_queuepii, vectorpii, greaterpii pq; dist.assign(graph.size(), INF); dist[s] 0; pq.push({0, s}); while (!pq.empty()) { int d pq.top().first, u pq.top().second; pq.pop(); if (d ! dist[u]) continue; for (auto edge : graph[u]) { int v edge.first, w edge.second; if (dist[u] w dist[v]) { dist[v] dist[u] w; pq.push({dist[v], v}); } } } }注意几个细节使用邻接表而不是邻接矩阵存储图因为稀疏图在竞赛题里更常见邻接表的时间空间复杂度都更有优势使用pairint, int而不是自定义结构体表示边省去重载运算符的麻烦if (d ! dist[u]) continue;这一行是堆优化Dijkstra的关键它避免了同一个节点被多次处理的无效循环。关于使用STL还是手写数据结构我的经验是能用STL就用STL除非题目明确要求卡常数。STL的priority_queue、vector、unordered_map在大多数情况下效率足够而且写得快、不易出错。只有线段树这种STL没有对应容器、或者并查集需要带权路径压缩扩展时才需要手写。手写数据结构时记得把数组大小开到MAXN的4倍线段树或者适当冗余防止越界访问导致的RE。还有一个经常被忽略的点模板代码一定要保证在没有使用C新特性的情况下也能通过编译。比赛环境通常支持C17但有些老牌赛事的评测环境可能还在用C11。大量使用auto、结构化绑定、std::optional等C17特性的代码遇到老编译器时会编译失败。整理模板时最好带上-stdc11标志编译一遍确认无误或者干脆遵守“只用C11特性”的原则。5. 排版与工具选型从代码仓库到一份赏心悦目的PDF模板写好了接下来就是排版输出。这一步很多人觉得无所谓反正内容一样。但排版质量直接影响你在赛场上的阅读体验字体大小、代码高亮、页边距这些细节都会影响翻查效率。而且一份排版精良的PDF也方便平时翻阅复习增强你继续维护它的动力。我先说工具链选型然后说排版建议。我的首选方案是用Markdown写代码片段再用Typora导出PDF。具体流程如下在VS Code里按目录结构整理每个模块的.md文件代码段用三个反引号包裹并标注cpp语言类型用Typora打开总文件通过“文件-导出-PDF”功能一键输出在Typora的主题设置里调整代码块的字体大小和行号显示保证打印时清晰可读。这套方案的优点是快速、好维护、代码高亮效果美观缺点是分页控制能力有限。如果你的模板很长某个模块刚好跨页断开不太美观但是不影响使用。如果你的排版需求更精细可以选择LaTeX。LaTeX的listings宏包可以优雅地排版代码支持自定义行号、边框、字体等。经典模板如下\documentclass[10pt,a4paper]{article} \usepackage[UTF8]{ctex} \usepackage{listings} \usepackage{xcolor} \lstset{ languageC, basicstyle\ttfamily\footnotesize, keywordstyle\color{blue}, commentstyle\color{gray}, numbersleft, numberstyle\tiny, framesingle, breaklinestrue, showstringspacesfalse } \begin{document} \section{线段树} \begin{lstlisting} // 线段树 - 区间最大字段和 // ... \end{lstlisting} \end{document}用LaTeX的好处是分页和样式控制非常精细坏处是学习成本高、调试时间长。如果你只是整理一份给自己用的模板我建议从Markdown方案开始等模板内容稳定之后如果有精力再迁移到LaTeX。这里分享我和队友踩过的一个坑我们曾经用公司常用的Word排版模板用代码字体手动缩进结果打印出来发现某些行因为对齐问题在换行处严重错乱比赛时找代码找得头大。从那以后我再也不用Word排代码了Markdown或者LaTeX二选一代码高亮和缩进交给工具去处理。打印方面建议正文字号用10pt或11ptA4纸双面打印左侧装订。代码行号保留方便团队讨论时说“翻到第27页的Dijkstra第15行”这种快速定位。还有一个经验多打印一份放宿舍备用或者带上U盘存一份PDF去现场以备临时需要查看某个模板但又没带纸质版的情况。6. 版本管理让模板像项目一样迭代模板不是一次成型的东西它需要随着训练和比赛不断迭代。刚开始你可能觉得“我写好了模板就大功告成了”但实际上经过几场比赛你一定会发现需要改的地方某段代码有bug、某个算法有更优的实现、某个边界情况需要补充处理。这些修改如果不做版本管理很容易出现“改了这处忘了那处”的混乱状态。我的做法是给模板目录建立一个Git仓库每次修改提交时写清楚变更内容比如“修复Dinic算法在重边情况下的流量计算bug”或“新增莫队算法模板支持带修改的区间查询”。长此以往这个Git提交历史本身就是你算法能力成长的一个时间线回头看会非常有成就感。另外建议在比赛前的两三天专门做一次模板评审。流程是这样的把所有模板都打开从头看一遍模拟自己在比赛中的使用场景检查有没有遗漏的边界情况处理有没有写错的复杂度注释有没有因为排版问题导致阅读困难的代码段。这一步我和队友通常花一个晚上完成每次都能发现至少一两个潜在问题。这个习惯还带来一个附加好处反复阅读模板会加深你对算法的理解。尤其是每次读到自己以前写的代码时你可能会想“这里当时为什么要写成这样”“这个边界条件是什么时候加上的”。这种思考对巩固算法知识非常有效。7. 实战检验模板在赛场上的使用心得模板整理完毕之后最终要经历实战检验。我根据自己的参赛经验把模板在赛场上的使用心得总结为以下几类场景每一类都对应不同的策略。7.1 赛前调试模板代码也要事先“跑一遍”很多新手犯的一个错误是模板代码整理完就当完成任务了从来没有在真实环境下编译运行过。等到比赛现场想用一个冷门算法模板时才发现代码里有语法错误或者变量名冲突比赛时间被白白浪费。我的建议是整理阶段每写完一个模板就立刻编译运行一遍喂几组简单的测试数据确认正确性。这一步不能省哪怕是最简单的快读模板也可能因为手误出问题。7.2 赛中翻找合理的目录和内容排版能救命比赛进行中时间压力极大你不可能像平时一样慢慢翻目录。我习惯的做法是赛前把模板PDF的目录页打印一份放在旁边然后根据题目类型预先判断可能会用到的模板先把对应页码折角方便快速翻查。如果一道题卡了很久我会停下来从头看一遍模板目录而不是死磕一道题——有时候换个角度可能就找到突破口了。需要特别提醒的是比赛中不要临时修改模板。如果你发现模板和当前题目的需求有出入不要在现场手忙脚乱地调整核心逻辑。正确做法是先用模板的函数结构搭好框架再另写辅助函数针对性处理差异保持模板不变。因为比赛时你改模板代码很容易引入新的bug而你又没有充足的时间做充分测试风险太高。7.3 赛后复盘模板更新的最佳时机每场比赛结束后的复盘阶段是模板更新的最佳时机。我会把比赛中踩过坑的地方、某些模板表现不佳的环节记录下来赛后针对性地修改模板。比如一次区域赛里我的后缀数组模板写得太长现场修改太耗时赛后我就重写了一个更精简的版本把不必要的参数都去掉了。复盘时还做一件事统计每场比赛使用了哪些模板。一年下来你会发现真正用到的高频模板其实就那么十来个其他很多是一次都没用过的“冷门货”。这时候就需要评估这些冷门模板是保留还是删除。我的标准是如果某个模板在连续三场正式比赛中都没用到但算法本身属于经典必考范围保留如果这个算法本身就很少出现比如伯努利数求和而且我对它不熟练删除。模板越精炼翻查效率越高。比赛结束后的另一个好习惯是检查有没有“因模板问题而丢分”的题。比如因为模板代码写得太长现场没时间改完或者因为模板里缺少某个边界情况处理导致样例过了但评测WA。这些问题远比“不会做”更让人惋惜因为它们本可以通过提前准备来避免。8. 进阶建议让模板成为团队协作的基石如果你在校队里模板往往不是个人的事情。很多学校采用“团队共享模板”的模式每个队员负责擅长的专题整理好自己的部分合在一起形成全队的统一模板。这样做有很明显的优点模板的覆盖面更广代码质量经过多人审阅而且打印时只需要打印一份共同使用。但也有一个非常现实的问题每个人的代码风格不一样合并到一起后你在赛场上用到的可能是队友整理的模板这时你需要花额外时间去理解它的结构和变量含义。所以我建议团队模板在合并前做一个成员交叉评审每个人负责的专题其他队友至少看一遍确认能大致看懂关键逻辑。交叉评审的时间成本很高但能避免赛场上“队友写的模板我看不懂”这种尴尬情况。如果队伍规模大还可以建立模板多人评审制度某个专题的整理者提交初稿另一名队员负责审阅并补充边界测试数据确认无误后才合入正式模板。这本质上是一个小型的代码评审流程对提高模板质量非常有帮助。除了团队协作把模板分享到社区也是促使自己提升的好方法。我自己在校队期间就把模板开源到了GitHub后来陆续收到一些学弟学妹的反馈有人指出某处边界条件考虑不周有人建议补充某个算法的替代实现。这些外部反馈逼着我反复审视和完善自己的代码对我的算法理解帮助很大。如果你觉得自己的模板还不够成熟不用怕先发一个“beta版”收到反馈再迭代就是最快的进步方式。9. 最后几个值得注意的坑写了这么多最后再把整理模板过程中最容易踩的几个坑单独拎出来提醒一遍。这些坑我都亲身踩过也因此找到了针对性的解决方案。第一个坑只收集不整理。从各处复制了几百页代码觉得“存下来就算会了”结果自己根本没写过、不理解、比赛时也不敢用。解决方案就是前面反复强调的每个模板必须自己亲手敲一遍并测试通过然后用自己的语言写注释。第二个坑伪注释。注释写得太长比代码本身还长甚至把算法的完整推导过程也塞进去。比赛时根本来不及读。模板注释只需要三行算法名、适用场景、复杂度最多再加一两个关键边界条件的提醒。第三个坑堆砌而非精选。同一个算法收集了四五种不同写法感觉自己“准备充分”。但实际比赛时选择困难本身就会消耗时间。每个核心知识点保留一个你最熟悉、最精简的版本就够了。第四个坑不更新。模板整理完就封存比赛出了bug也不修改下一场比赛继续踩同一个坑。模板必须保持“社区活跃度”——每次比赛、每次组队训练之后都主动审视一遍有修改就顺手改掉并同步更新PDF。第五个坑忽视环境差异。平时在自己电脑上编译运行没问题但比赛机器上的编译器版本、头文件路径、环境变量可能都不一样。准备模板时在至少两个不同环境比如本机、队友电脑、比赛机房编译测试一遍保证代码的可移植性。第六个坑打印排版混乱。代码宽度超出一行导致打印后换行错乱、代码行号缺失导致定位困难、页边距不合适导致装订后部分代码被遮挡。这些问题都在赛前打印时就能发现所以建议正式比赛前至少提前一周完成打印检查不要在比赛前一晚才匆忙打印。如果你现在正在准备自己的第一份ACM模板我的建议很简单今天就开始哪怕只整理一个你最熟悉的算法。从一个小而美的模板开始在训练和比赛中持续迭代这个PDF会随着你的成长而成长。等到它能让你在赛场上多AC一道题、少走一次弯路你就知道这几十个小时的投入有多值得了。本文还有配套的精品资源点击获取