算法竞赛入门经典例题答案:从对答案到独立解题的完整指南
简介这份资源是《算法竞赛入门经典第二版》例题答案与配套代码合集面向准备ACM/ICPC等算法竞赛的初学者与进阶选手帮助读者对照书中例题检验思路、补齐实现细节。压缩包共224个文件以191个cpp源码为主体另含12个in输入样例、10个ans标准答案及11个md说明文档整体约208KB体量轻便便于按章节检索与本地调试。内容覆盖排序与搜索、动态规划、图论最短路与最小生成树、KMP与Manacher等字符串算法、数论与组合数学、树与哈希表等数据结构以及贪心、递归分治、快速IO与位运算等编码技巧并配有模拟类题目的实现参考。已有2003人学习下载适合作为刷题时的对照答案与排错参考也可用于系统梳理竞赛知识点、提升代码实现与问题解决能力。1. 算法竞赛入门经典第二版例题答案为什么“对答案”比“刷题”更容易卡住很多人第一次翻开《算法竞赛入门经典第二版》会默认一件事把例题代码敲一遍、跑通样例就算学过了。真正动手才发现卡住的地方往往不是题不会做而是答案对不上——样例过了提交却错或者干脆连题目在问什么都没读明白。这本书的例题答案本质上不是一份“标准输出”而是一套把算法思想翻译成可运行代码的示范它解决的是“我知道这个算法但不知道怎么写进这道题”的断层。这份内容适合两类人一类是刚接触算法竞赛、需要靠例题建立手感的入门者另一类是刷过一些题、但代码总是差一口气、想回头把基础打牢的从业者。它不承诺让你速成但能让你在“读题—建模—写码—调错”这条链路上少绕几个弯。下面我会按“怎么用答案、怎么复现、怎么避坑、怎么进阶”的顺序把这件事拆开讲清楚。2. 例题答案到底该怎么用从读题到跑通的最小闭环2.1 先分清三类例题别用同一种方式对答案这本书的例题大致可以分成三类对答案的方式完全不同。第一类是语法与输入输出训练题比如简单的循环、数组操作这类题答案的价值在于让你熟悉 C/C 的写法习惯重点看输入输出格式和边界处理。第二类是经典算法模板题比如排序、二分、递归、动态规划入门这类题答案的核心是状态定义和转移方程代码只是载体。第三类是综合应用题往往把多个知识点串在一起答案里会有一些“看起来没必要”的中间变量那些恰恰是理解题意的关键。我一般会先扫一遍题目判断它属于哪一类再决定是“先自己想 10 分钟再看答案”还是“直接看答案反推思路”。语法题可以直接看算法模板题至少要先写出状态定义综合题则建议先画个草图再对答案。这个判断过程本身就是在训练读题能力比盲目敲代码有用得多。2.2 用最小可运行框架跑通第一道例题不管题目多简单我都建议先搭一个最小可运行框架把输入输出跑通再往里填逻辑。下面这个框架适用于大多数单组输入的例题#include stdio.h int main() { int n; // 读取输入注意返回值判断避免空输入导致未定义行为 if (scanf(%d, n) ! 1) { return 0; } // 这里先写一个占位输出确认输入输出通路正常 printf(%d\n, n); return 0; }这段代码的逻辑很简单先保证程序能正确读到输入再原样输出。参数说明上scanf的返回值是成功读入的变量个数判断它等于 1 可以避免读到文件尾时还继续执行。很多初学者一上来就写完整逻辑结果样例不过分不清是算法错了还是输入没读对。先用这个框架跑通再替换中间的占位输出排错成本会低很多。2.3 对答案时重点看三处边界、变量名、循环范围对答案不是逐行比对而是重点看三处。第一处是边界处理比如数组开多大、循环从 0 还是 1 开始、有没有处理 n0 或 n1 的情况。第二处是变量名答案里的变量名往往暗示了它的含义比如cnt是计数、sum是累加、dp是状态数组看懂变量名能帮你快速理解思路。第三处是循环范围i n和i n差一个等号结果可能完全不一样。我见过不少初学者答案跑通了但自己写就错原因就是只记住了代码形状没理解这三处为什么这么写。建议对完答案后把这三处用自己的话注释一遍再关掉答案重写一次。能重写出来才算真正对上了。3. 把例题答案变成自己的代码复现、改写与验证3.1 复现关掉答案从空白文件开始写复现是检验是否真正理解答案的唯一标准。具体做法是打开一个空白文件只看题目描述不看答案从头写一遍。写的过程中如果卡住不要立刻翻答案先标记卡住的位置继续往下写能写的部分。写完后再对照答案看卡住的地方差在哪里。这个过程很痛苦但效果最好。我一般会给自己定一个规则简单题 15 分钟内复现中等题 30 分钟超过时间就先看答案但看完必须再复现一次。复现时注意不要背代码而是回忆思路这道题的状态是什么、转移是怎么发生的、边界是怎么处理的。思路对了代码自然能写出来。3.2 改写换一种写法验证理解深度复现之后可以尝试改写。比如答案用递归你改成迭代答案用数组你改成指针答案用 C 风格输入输出你改成 C 的cin/cout。改写的目的是验证你是否理解算法的本质而不是记住某一种写法。下面是一个递归改迭代的简单例子以阶乘为例// 递归版本 int fact_rec(int n) { if (n 1) return 1; return n * fact_rec(n - 1); } // 迭代版本 int fact_iter(int n) { int res 1; for (int i 2; i n; i) { res * i; } return res; }逻辑说明递归版本依赖函数调用栈迭代版本用循环变量累积结果。参数上n较小时两者差别不大但n很大时递归可能栈溢出迭代更安全。改写时要注意整数溢出问题阶乘增长很快实际题目中通常会取模。通过这种改写你能更清楚地看到算法在不同实现方式下的取舍。3.3 验证用对拍和边界数据代替“样例过了就行”样例只能验证最理想的情况真正靠谱的验证是对拍和边界数据。对拍的做法是写一个暴力版本保证正确但可能很慢再写一个优化版本随机生成大量小规模数据比较两者输出是否一致。边界数据则包括最小值、最大值、特殊值如 0、负数、重复元素。下面是一个简单的对拍脚本框架用 bash 写#!/bin/bash # 对拍脚本比较暴力版本和优化版本的输出 for i in $(seq 1 1000); do # 生成随机输入这里以 1 到 100 的随机数为例 echo $((RANDOM % 100 1)) input.txt ./brute input.txt out_brute.txt ./fast input.txt out_fast.txt if ! diff -q out_brute.txt out_fast.txt /dev/null; then echo 发现不一致输入为 cat input.txt break fi done echo 对拍完成逻辑说明脚本循环生成随机输入分别喂给两个程序用diff比较输出。参数上循环次数可以根据需要调整随机数范围要覆盖题目可能的输入范围。注意生成的数据要符合题目约束否则可能两个程序都错得一样对拍就失去意义了。对拍能发现很多样例覆盖不到的边界问题是算法竞赛中非常实用的验证手段。4. 避坑与排查例题答案里最容易翻车的五个地方4.1 样例过了但提交错多半是输入输出格式没对齐现象本地跑样例输出和答案一模一样提交后却返回错误。原因通常是输入输出格式有细微差别比如多输出一个空格、少输出一个换行、大小写不一致、或者题目要求多组输入但代码只处理了一组。解决方法是仔细读题目的输入输出说明用printf时注意格式串多组输入要用while (scanf(...) ...)循环处理。4.2 数组越界答案里没写但你不能不防现象程序在小数据下正常数据一大就崩溃或结果异常。原因是数组开小了或者循环变量超出了数组范围。答案里有时为了简洁会省略边界检查但实际提交时必须自己加上。解决方法是根据题目数据范围估算数组大小循环条件用i n而不是i n访问数组前确认下标有效。4.3 整数溢出int 不够用的时候要换 long long现象计算结果和预期差很多甚至变成负数。原因是中间结果超过了int的范围。算法竞赛中常见的累加、乘积、组合数都容易溢出。解决方法是估算最大可能值超过 2×10^9 就换long long输出时用%lld。注意long long也不是无限的特别大的数可能需要取模或高精度。4.4 递归太深导致栈溢出该改迭代就改迭代现象递归程序在数据规模较大时崩溃报段错误。原因是递归深度超过了系统栈的限制。解决方法是把递归改成迭代或者手动模拟栈或者增大栈空间但竞赛环境通常不允许。常见的深递归场景包括 DFS 遍历大图、递归求阶乘等能改迭代就改迭代。4.5 浮点数比较不要用 判断相等现象浮点数计算结果明明应该是某个值但条件判断就是不成立。原因是浮点数存在精度误差。解决方法是设置一个极小值eps用fabs(a - b) eps来判断相等。eps一般取 1e-8 或 1e-6根据题目精度要求调整。输出时也要注意保留小数位数用%.2f这类格式控制。5. 从例题答案到独立解题一个可复用的训练习惯5.1 建立自己的“错题-思路”对照表对完答案后不要只把正确代码存下来而是建一个对照表记录三列题目编号、我原来的错误思路、正确思路的关键点。比如某道动态规划题我原来想的是贪心正确思路是状态转移关键点是“当前状态只依赖前一个状态”。这个表不用很正式一个 Markdown 文件就够。积累几十道题后你会发现自己反复卡在同样的思维盲区上这时候针对性补强效率比盲目刷题高得多。5.2 用“三遍法”吃透一道例题第一遍看答案理解思路跑通代码。第二遍关掉答案从空白文件复现卡住就标记。第三遍改写或优化比如换一种数据结构、降低时间复杂度。三遍下来这道题才算真正进入你的工具箱。我一般会把这三遍的代码分别保存过一周再回头看如果还能快速写出第三遍的版本说明确实掌握了。5.3 把例题答案当成“参考实现”而不是“唯一解”书上的答案是一种参考实现不是唯一正确的写法。同一道题可能有多种解法时间复杂度和空间复杂度各不相同。对答案时可以想一想有没有更快的做法有没有更省内存的做法如果题目数据范围变了我的代码还能不能跑这种追问能帮你从“会做这道题”过渡到“会做这类题”。我自己的习惯是每做完一道例题至少想一种替代方案哪怕不写出来也要在脑子里过一遍。5.4 一个具体的进阶技巧用调试输出定位逻辑错误当程序结果不对时不要盯着代码干看而是加调试输出。具体做法是在关键变量变化的地方打印它们的值和预期值对比。比如动态规划题打印每一步的dp数组图论题打印每次入队出队的节点。下面是一个调试输出的例子// 在动态规划循环中加入调试输出 for (int i 1; i n; i) { dp[i] dp[i - 1] a[i]; // 打印当前状态方便和手算结果对比 printf(dp[%d] %d\n, i, dp[i]); }逻辑说明调试输出能让你看到程序实际执行路径而不是你以为的执行路径。参数上打印的变量要选关键的不要什么都打否则输出太多反而干扰判断。定位到问题后记得把调试输出删掉或注释掉避免影响最终提交。这个习惯看起来笨但在排查复杂逻辑错误时非常有效。希望帮到你。本文还有配套的精品资源点击获取