
那年夏天我还在学校实验室里天天刷题偶然看到CodeM 2017美团编程大赛的消息。当时只是抱着“试试水”的心态报名了初赛没想到一路走到了复赛。说实话这几年算法竞赛越来越卷但CodeM的题目质量和赛制安排一直给我留下很深的印象——它不是单纯让你背模板而是真的把业务场景里的复杂问题抽象成算法题做起来很有代入感。这篇博文就围绕CodeM 2017复赛把从备战到上场的完整经历、题型拆解和踩坑记录都整理出来给以后想参加类似比赛的同学一个参考。CodeM是美团主办的面向全球大学生的编程竞赛2017年是第二届赛程分为在线初赛、复赛和决赛。初赛题目偏经典算法考察基础功底复赛则明显上了一个台阶不仅题量更大而且每道题都裹着业务的外衣需要先读懂“故事”再抽象出数学模型。如果你正在准备这类比赛或者想看看一线互联网公司的编程赛事到底考什么这篇文章值得花几分钟读完。1. CodeM 2017整体赛制与复赛定位1.1 从初赛到复赛晋级的真实门槛CodeM 2017分为资格赛、初赛和复赛三级。资格赛基本上是“门槛友好型”只要有一定编程基础、能写出正确的暴力解法就有机会进入初赛。初赛通常是限时两到三小时需要在线上平台完成若干道算法题按正确率和用时排名。我当时所在的赛区竞争挺激烈身边不少ACM银牌选手都在同场竞技所以初赛淘汰率并不低。真正拉开差距的是复赛。复赛的题目设计不再只是“套模板”而是刻意模拟美团实际业务中会遇到的计算问题。比如配送路径规划、商品组合推荐、骑手排班调度这些问题在教科书里都能找到原型但加上数据规模和边界条件之后难度完全不同。复赛的入围人数通常控制在几百人这意味着每道题基本都是“硬仗”靠碰运气或者背诵题解是过不去的。我记得复赛当天一共四道题时间四小时题目从易到难分布并不均匀。第一题算是热身但也不是白给第二三题开始需要认真建模最后一题基本是给顶尖选手准备的压轴题大部分人在最后半小时只能拿到部分分。这个设置比较考验策略——如果一开始死磕难题很可能连中等题都顾不上排名反而不理想。1.2 复赛为什么值得认真对待单纯从奖品角度说CodeM 2017的复赛奖励就已经很有吸引力有现金奖金、决赛直通名额还有美团的校招绿卡。但我觉得更重要的价值在于这个比赛给了参赛者一个和真实业务问题近距离接触的机会。很多学校的算法训练停留在LeetCode或者OJ模板题层面而CodeM复赛的题目会给你一个“业务场景描述输入输出约束”你得自己判断用什么模型、怎么优化算力、如何处理极端数据。这种能力刚好是校招面试中比较稀缺的。后来我参加美团面试时面试官知道我在CodeM复赛中的表现就主动聊起其中某道题的解法问我是怎么考虑复杂度的。整个对话非常自然因为比赛题目本身就和部门业务有相通的地方。可以说CodeM复赛的参赛经历不仅是一张成绩单更是一个让你在面试里“有话聊”的素材。另外复赛的评论区也是个宝藏。比赛结束后官方会公布题解和标程参赛者们在讨论区里会分享各种奇妙的思路。我复赛后在讨论区蹲了好几天看到有人用网络流解配送问题也有人用线段树优化DP思路相当开阔。这种“赛后比完赛更精彩”的氛围是其他很多比赛做不到的。2. 复赛题型深度拆解与解题思路2.1 四道题的难度分布与“送分题”陷阱CodeM 2017复赛的题目虽然保密但回顾下来题型大致可以分为四类基础数据结构题、字符串处理题、动态规划题、以及图论综合题。如果你准备过其他编程大赛会对这个结构似曾相识但CodeM特别喜欢在“输入规模”上做文章这也是最容易让人翻车的地方。先看第一类题表面上考的是哈希或排序数据范围看着也很温和可你一旦提交就发现有几个隐藏的大数据测试点。因为题面里可能写着“字符串长度不超过10^5”但没说总长度之和不能超过10^7如果你只按单次输入规模设计算法很容易TLE。我当时就在这类题上吃过亏本以为自己用了O(n log n)已经够稳结果因为常数太大最终只有部分分。第二类题通常涉及字符串匹配或字典树这一类其实有套路但CodeM不会让你轻松套板子。它会加一些动态修改操作比如在字符串中间插入字符、删除子串然后问你某个模式串出现了多少次。这就逼着你必须考虑可持久化数据结构或者分块一个简单的KMP根本扛不住。所以说复赛的“送分题”其实是伪装过的中等偏难题千万别掉以轻心。2.2 动态规划题状态设计往往决定生死CodeM复赛的DP题是我印象最深的。它不是标准的背包或者区间DP而是会加上一个业务限制条件。比如有一道题大概意思是配送员一次可以带多个订单但每个订单有截止时间问最多能完成多少订单。表面上很像经典的“任务调度最大化完成数量”问题很多人第一反应就是贪心排序加优先队列。但实际上题目里有个隐含条件骑手在不同商家之间的转移时间是不对称的。这就把问题变成了带依赖关系的调度单纯贪心不成立。我当时的做法是先按截止时间排序然后DP记录“当前时间下能完成的最大单量”用滚动数组压缩状态再用树状数组维护转移最大值。整体复杂度压到了O(n log n)这才勉强通过。这道题给我最大的启发是复赛里DP一定不能只背模板状态定义要围绕题目给的业务条件来设计。你不需要炫技般地把状态设得多复杂关键是找到能覆盖所有约束的最小状态集。比如上面那题核心状态就是“当前订单数量”和“累计耗时”其他信息都可以从输入预处理里得到能省略就省略。2.3 图论综合题从“最小生成树”到“多约束最短路”的跨越复赛压轴题一般离不开图论。2017年那届的压轴题我印象里和“在图上求满足多个约束的最短路径”相关而且约束条件可以叠加。比如路径不仅要考虑距离最短还要考虑某几种颜色节点的数量配比或者在某些时间窗口内必须经过指定区域。这类题考的是对经典算法的变种理解你不能只背Dijkstra模板得知道怎么在维护距离的同时维护一个多维状态空间。我当时在Dijkstra里加了一个“状态压缩分层图”的思路把每个约束条件拆成一个维度把图复制成若干层在层间转移时处理约束切换。这样虽然能保证正确性但代价是状态数暴增一不小心就MLE。后来我优化了一些不必要的层次只在真正需要跨层的地方才建边内存才勉强够用。如果你们也想冲击这类题我的建议是先把经典算法的每个细节吃透特别是堆优化的原理和状态转移的单调性。然后多看看分层图、状态压缩这类进阶技巧的典型题例不要等比赛时再临时想。CodeM不是靠灵光一闪就能拿分的比赛它考察的就是你平时积累的“武器库”够不够全。2.4 部分分的正确打开方式不能AC也绝不能0分虽然大家都想AK但在复赛这种难度下绝大多数选手是拿不满分的。所以“拿部分分”本身就是一项策略能力。CodeM的评测机制通常支持“按测试点给分”部分正确也能得分这就给你留了不少操作空间。我的习惯是这样的先快速把所有题目的暴力解法写出来哪怕是O(n^2)甚至O(2^n)只求能过最小的数据点。写完暴力后再挑出自己最有把握的一题尝试优化到标准解法。这样做的好处是至少保证了每道题都有分不会出现“一题卡死整场崩盘”的情况。如果有剩余时间再回头去优化其他题。这个过程听起来简单但实际操作时很考验心态。因为四道题都摆在眼前人很容易贪心总想着“我能不能把第二题AC了再去做第三题”。结果往往是第二题卡了一个小时第三题连暴力都没写出来最后每道题都只有零星几分。正确做法是给自己定一个硬性时间表前四十分钟完成一二两题暴力一个小时内尝试第二题正解最后两小时再分配给三四题。3. 我的复赛备战路线与现场实录3.1 赛前一个月用什么方式刷题最有效我知道很多人备赛喜欢大量刷题一天做十道LeetCode觉得很有成就感。但针对CodeM复赛光刷数量没用得按“题型的业务包装”来练。我当时把时间分成三块。第一块是刷经典算法模板包括线段树、树状数组、并查集、KMP、AC自动机、最大流、费用流这些是基本功必须保证随手就能写出无bug版本。第二块是专门找“有场景包装”的中等偏难题比如Codeforces上一些带故事的构造题以及国内外大厂笔试题训练自己从文字描述中抽取出算法模型的直觉。第三块是模拟比赛严格按四小时限时、四道题的模式训练用官方或者往年的模拟题。模拟比赛特别重要因为在家里刷题和在赛场上写代码完全是两回事。赛场上有时间压力、有排名压力还有网络波动和平台报错等突发状况。你只有在平时经历过这些现场才不会慌乱。我记得有一次模拟训练时平台的Judge系统突然抽风导致我的提交一直显示“Pending”等了十分钟才出来结果。那次之后我学会了在代码里提前写好文件读写的模板尽量一键编译运行减少对评测平台的依赖。3.2 复赛当天环境准备要做的几件事复赛当天是线上比赛环境准备比想象中更重要。比赛平台一般是Web IDE但你也可以用本地IDE写完再上传关键是保证本地环境与评测机一致。我在比赛前半小时就做了一件事建一个空白项目把常用的算法模板全部写好——快读快写、离散化、并查集、树状数组、Dijkstra堆优化、网络流Dinic模板全部预编译一遍确保在本地能一键运行。这能省下比赛期间大量打模板的时间把精力集中在思考题目上。另外要注意的是网络和账号问题。线上比赛最怕的就是写了一半掉线或者提交时因为网络延迟超时。我通常的做法是准备好一个稳定的网络环境关掉不必要的自动更新和后台下载另外在比赛开始前登入平台确认账号状态正常熟悉一下代码编辑器和交题界面的位置。别小看这些琐事真到了争分夺秒的时候任何一个小卡顿都可能打乱你的节奏。还有一点很多线上比赛允许使用本地的编译器和调试工具但要注意别依赖那些在评测机上没有的库或特性。比如某些评测环境是GCC 4.8你本地用的C17特性可能编译不过。为了保险我在代码里基本只写C11标准而且避免使用一些冷门库函数。倒是常用的bits/stdc.h这种万能头文件CodeM的评测环境一般支持不过保险起见也可以把需要的头文件列出来提前编译测试。3.3 时间分配与提交策略从“写代码”到“抢分数”复赛四小时看起来不短但实际做下来会觉得时间严重不够用。我的策略是前10分钟快速浏览四道题标出每道题的数据规模和大概难度然后按“暴力先行、正解跟进、难题后置”的顺序来做。开场后我给每道题写了至少一个拿部分分的暴力版本保证基础分到手然后回头选择自己最熟悉的题型做正解优化。印象最深的是第二题字符串题我一开始想用后缀自动机但写着写着发现自己对构建细节记不太清了马上切换思路改用后缀数组加二分虽然复杂度稍高但实现稳妥得多。这个“及时止损”的判断非常关键——如果我一直纠结后缀自动机的细节可能一个小时就耗进去最后一题连看的机会都没有。提交的时候也有技巧。不要等到代码写完整才去提交可以在实现完关键函数后先提交一次“半成品”看看测试点的得分情况。CodeM的评测通常会显示每个测试点的通过情况这样你能快速发现自己哪里写错了是边界条件还是超时。这种“先交一版观察反馈再迭代优化”的策略比闷头写完一次性提交要高效得多。3.4 复盘比赛结束后一定要做的事比赛结束并不意味着学习结束。我强烈建议赛后第二天、趁记忆还清晰的时候立刻把每道题的思路和代码重新整理一遍。即使你AC了也值得看看官方题解和其他选手的提交学习更优雅的解法。你会发现自己花半小时调出来的代码别人可能只用二十行就完成了这种差距就是成长的空间。我复赛结束后看了好几份排名靠前选手的代码最大的感触是他们的代码非常“干净”变量命名清晰逻辑分段明确甚至还有注释。这不仅仅是代码风格问题它反映了对题目的理解深度。代码写得乱的人往往是因为思路本身就模糊而思路清晰的人代码自然有条理。从那时起我开始有意识地训练自己快速写出可读性强的代码这在后来的面试和工作中都受益匪浅。4. 参赛过程中常见的坑与排查方法4.1 大数据范围下的输入输出性能瓶颈线上算法赛里最容易出现的坑之一就是输入输出耗时远远大于算法本身耗时。Java选手可能体会更深Scanner读入超大输入的时候那速度慢到让人怀疑人生。即使是C如果使用cin且没有关闭同步数据量一大也容易超时。我当时反正是直接用自写的快读模板用fread批量读入再加上手写整数解析速度比cin快好几倍。在这里也提醒一下C选手如果你确实喜欢用cin/cout一定要在main开头加上ios::sync_with_stdio(false)和cin.tie(0)能明显提升IO效率。但还是建议准备一份快读模板因为到了大数据量题目里这点效率可能就是AC和TLE的分界线。4.2 内存超限常见但不是无解的难题复赛题目普遍数据规模大内存限制却控制得比较紧64MB或128MB很常见。写代码时如果不注意vector套vector非常容易爆内存。我记得比赛时有一道DP题我当时想开一个二维数组存状态算了下需要两亿个int内存瞬间爆炸。后来改用滚动数组加稀疏存储才把内存压了下来。排查内存问题一定要学会估算一个int是4字节long long是8字节一亿个int就是400MB轻松超限。所以看到数据范围先心算一下内存再决定数据结构的选择。能用位压缩就用位压缩能用short就不开int这些平时轮不到的小技巧在内存紧的时候都是保命技能。4.3 评测环境的差异本地通过不代表评测机通过这是线上赛最玄学也最常见的坑。本地运行一切正常一交上去直接Runtime Error或者答案错误。原因五花八门可能是你用了未定义行为、依赖了某个未初始化的变量也可能是评测机的编译器版本和你本地不同还有可能是递归深度过大导致栈溢出。2017年那会儿评测环境还是32位的也有可能出现。我的排查经验是如果提交后报错第一时间检查数组是否越界、是否有除零、是否递归层数太多。这几类问题最常出现而且本地调试时不容易暴露因为内存布局差异会掩盖错误。如果时间充裕可以构造大数据随机测试对比暴力和正解的结果逐步缩小出错范围。这个方法虽然笨但在赛场上非常管用。4.4 心态崩了怎么办止损比死磕更重要线上比赛的另一个大坑就是心态。你可能在某一题上卡了四十分钟抬头一看排名好多人已经AC了两三题这时候很容易急一急就开始乱改代码越改越乱。我自己的经验是一旦发现自己在一个题上超过二十分钟没有进展就立刻停下来强制自己离开键盘喝口水深呼吸一分钟然后重新阅读题目和已写的代码。很多时候卡住的真正原因是漏看了一个关键约束或者初期的建模方向就是错的这时候不是继续调代码能解决的。如果你的目标是拿高分就要记住“总分最大化”比“单题AC”更符合你的利益。一道中等题的AC分数可能和一道难题的部分分差不多但耗费的时间差好几倍。所以在比赛后半段我一般会优先确保已经做出来的题不再扣分——检查边界条件、再跑几个极端case而不是再去啃没做完的难题。稳住的分数才是你的没做出来的题永远充满不确定性。5. CodeM复赛对技术成长和职业发展的长期影响5.1 算法思维在业务开发中的实际用处很多人觉得竞赛算法和工作内容脱节其实不然。参加CodeM复赛之后我在后来实习和工作中多次体会到算法思维的价值。比如有一回做物流调度相关的需求需要设计一个近似最优的派单策略当时我脑子里立刻浮现出CodeM复赛那道配送问题的“状态设计”思路。虽然业务系统不会真的跑Dijkstra和网络流但那种“约束条件怎么转化、状态怎么压缩”的思路可以帮你在面对模糊需求时更有章法。更明显的变化在代码效率意识上。竞赛时为了过大数据你会非常在意复杂度和常数。这种习惯带入工作后写接口时自然会考虑会不会有性能瓶颈做数据同步时会想到批处理和流式处理的选择。很多同事写代码只求功能正确线上数据一多就卡死而你有过竞赛训练写出来的方案从一开始就会预留性能空间这就是隐性优势。5.2 写在简历上的正确姿势不只是“参加了比赛”如果你也打算把CodeM复赛经历写进简历千万别只写一行“参加CodeM 2017复赛”。更好的做法是点出比赛规模和成绩比如“从X万参赛者中晋级复赛排名前X%”再结合一个具体题目简要说明你的解法和复杂度。这样面试官一眼就能看出你的算法能力而不是把比赛经历当成一个虚无缥缈的奖项。另外可以把比赛中的代码整理成GitHub仓库写清楚每道题的题意、思路、复杂度分析和代码注释。这不只是为了展示更是自己复盘的过程。我在整理CodeM复赛题解时重新思考了很多当时赶时间没想透的细节那些思考反而比比赛本身更有收获。5.3 适合谁去参加这类比赛最后聊一下适合谁的问题。如果你是非计算机专业但想通过校招进入互联网公司算法比赛是证明编程能力最直接的方式之一。CodeM的初赛门槛不高你可以在不脱产的情况下参与即便只进复赛简历上的分量也足够了。如果你本来就是ACM选手那CodeM这类偏业务场景的题目更是值得练手因为它会让你从“会算法”过渡到“会用算法”。当然我也要泼一盆冷水如果连基础的数据结构和算法都没掌握建议先不要直接冲复赛。先把LeetCode上的常见题型刷过一遍掌握链表、树、图、DP这些基础模块再报名参加比赛否则体验会很受挫。比赛应该是验证能力的地方而不是你第一次接触某类算法的地方。CodeM 2017复赛是我第一次认真准备并完整参与的一场商业公司组织的编程大赛。回头看它带给我的不只是几道题的理解更像是一次“算法场景”的训练营。如果你也想挑战这类比赛我个人的建议是不要只看重名次而是把赛前准备、现场决策、赛后复盘整套流程都走一遍。这个过程对你的编程能力、问题抽象能力和抗压能力都是非常实在的锻炼。最后再分享一个小技巧赛后一定要去读官方题解和顶尖选手的代码收获往往比比赛现场还大。祝明年参赛的你们都能在复赛的考场里写出让自己满意的AC。