尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

0x3f3f3f3f、DP初始化与二分边界:算法竞赛一小时高效复习法

0x3f3f3f3f、DP初始化与二分边界:算法竞赛一小时高效复习法 0x3f 这个常量估计每个刷算法竞赛的人都眼熟得不行——0x3f3f3f3f就是那种“一看就懂一写就稳”的无穷大。今天是我冲刺备战的第39天早上 9:13 到 10:13 刚好挤出一个完整的复习一小时标题里的时间就是这么来的。这篇不是题解也不是教程就是把我这 60 分钟到底怎么复习、复习了啥、中间踩了哪些坑完整摊开写一写。如果你也正处于刷题停滞期、或者距离某场比赛没剩多少天却感觉前面学的东西在快速遗忘这篇应该能给你一套可以直接用的复习思路。1. 第39天为什么该停下来复习不是偷懒是战略1.1 39天这个时间点刚好卡在“遗忘曲线”的陡坡上很多人刷题有个惯性题目量最重要今天不刷三道新题就浑身难受。但到了第39天这个节点你会发现一个很尴尬的事实——第5天做过的二分模板写起来开始手生了第12天总结的最短路堆优化边界条件要想半天第20天明明弄懂的DP状态转移再看一眼居然有点陌生。这不是你退步了而是遗忘曲线在正常发挥作用。我之前对自己的要求是“每天至少两道新题”连续刷了一个多月正确率确实在涨但速度明显慢了。后来我意识到输入太多、巩固太少脑子里全是零散的题号形不成体系。所以第39天我特意把闹钟定在 9:13给自己整整一小时只做复习不碰新题就是为了在遗忘还没完全发生的时候主动回头拉一把。1.2 复习的投入产出比其实比刷新题高得多很多人不愿意复习是因为“复习没有成就感”——没有新题数的增长打卡日记不知道写什么。但实际算一笔账刷新题你大概率会遇到不熟悉的知识点查题解、调试、理解至少花 1-2 小时最后沉淀下来可能只有“这题我用XX方法过了”一句话而复习因为代码和思路都明明学过你完全可以在一小时里把过去一周的薄弱点全部过一遍顺手把模板修正成最适合自己的版本。我这次复习最大的感受是一个小时把三个核心知识点都重新拾起来还揪出了一个之前隐藏很深的 bug 习惯。这种效率刷三道水题都换不来。1.3 9:13-10:13 这种精确到分钟的打卡本身就是一种约束有人会觉得“复习嘛随便翻翻就行”但我建议你像我一样给复习也定一个硬性的起止时间精确到分钟。为什么因为模糊的时间安排没有安排。你说“上午复习一下”多半会变成“先刷五分钟手机再翻题解然后被某个新知识点带跑一小时后发现自己在下拉视频”。而 9:13 到 10:13 这个时间盒一旦定下来你的大脑在 9:13 就会自动进入“一小时窗口期”状态所有动作都被迫聚焦。这跟比赛时的倒计时逻辑是一样的——有了截止时间效率会不由自主地提起来。2. 这一小时复习了什么三张容易写错的“常考面孔”复习不能漫无目的一定得有清单。我这 60 分钟的清单是从过去一周的错题和卡壳点里筛出来的最后浓缩成三个主题最短路里的无穷大设置、DP的状态初始化、二分的边界处理。这三个东西共同点很明显代码量都不大但错起来都是悄悄摸摸的。2.1 最短路里的 0x3f3f3f3f无穷大的选型与加法溢出先说我最想聊的也是最贴合标题主题的——0x3f3f3f3f这个无穷大到底为什么好用。const int INF 0x3f3f3f3f; int dist[N]; memset(dist, 0x3f, sizeof(dist));很多人背下了这个写法但没想过它背后的两个关键点第一0x3f3f3f3f的值大约是 1,061,109,567比INT_MAX的 2,147,483,647 小很多但比普通题目的数据范围又大得多。这意味着它既能当“不可达”的标识又不至于大得离谱。第二memset(dist, 0x3f, sizeof(dist))是按字节填充一个字节填0x3f四个字节拼起来正好是0x3f3f3f3f。这就是为什么很多教材都说“要初始化成 INF 就写 memset 0x3f”因为别的值没法这样一行搞定。更妙的点是加法溢出问题。在 Dijkstra 里你一定会写类似d[v] w d[u]的判断如果d[v]是INT_MAX加上任意正权边后直接就溢出了可能变成负数导致一堆根本不可达的点莫名其妙被更新而0x3f3f3f3f 0x3f3f3f3f大概是 2,122,219,134依然小于INT_MAX两个 INF 相加都不会爆。就冲这一点选它当无穷大就是省心事。复习的时候我特意把这层加法和溢出关系在白纸上推了一遍比单纯背memset写法有用得多。2.2 DP状态设计别把“初始化”当口号复习动态规划时我最常犯的错不是状态定义而是初始化。比如线性 DP、背包 DP很多人上来就f[0] 0然后for循环走起但没想清楚“哪些状态是合法的起点哪些状态应该表示成无效态”。拿最长上升子序列举例如果你把dp[i]定义成“以 i 结尾的最长上升子序列长度”那初始化每个dp[i] 1才是对的因为单独一个元素本身就是一个长度为 1 的上升子序列但如果你拿到的是“恰好装满的背包问题”初始化就得小心——dp[0] 0, dp[1...V] -INF否则你会把“没装满”的方案也当成了合法答案。复习的时候我给自己提了个要求每看一个 DP 题先不写转移方程而是先写出“初始状态是什么、为什么是这个值、哪些状态永远不能达到”。这套动作做完再回头看转移方程思路就清晰很多。状态初始化不只是“代码第一行”它本质上是你在声明“这个问题从哪个合法世界开始演化”。2.3 二分答案左闭右闭与 mid 的生死选择二分是我第39天复习的第三个重点因为它堪称“边界条件重灾区”。很多人背了模板就上然后死在mid到底是(l r) / 2还是(l r 1) / 2死在while (l r)还是while (l r)。我现在的建议是固定一套写法每次都用同一套不要来回切换。比如我最常用的左闭右闭版本while (l r) { int mid (l r 1) / 2; if (check(mid)) l mid; else r mid - 1; }如果mid满足条件说明答案在[mid, r]所以l mid因为mid可能等于l所以必须用(l r 1) / 2向上取整否则当l 1 r时(l r) / 2会等于l然后l mid就死循环了。这些细节复习的时候不能只看要写。我就在纸上把l1, r2, check(1)true, check(2)false这种小样例手推了一遍比背十遍模板都管用。3. 复习不是重做我的错题回看方案和踩坑实录3.1 翻错题时的两个坏习惯复习最容易掉进的坑是把“复习错题”做成了“重做错题”。我以前就踩过翻到一道当时 WA 了三次的最短路题二话不说打开编辑器开始重新敲20 分钟敲完发现这次一遍过了还挺有成就感。后来一想这跟第一次做题有啥区别除了把代码复述一遍我并没有搞懂当时为什么错。第二个坏习惯是直接翻题解。错题旁边如果留了当时 AC 的代码或者题解链接人就有惰性扫一眼“哦原来这样啊”然后合上本子啥也没记住。真正有效的复习是逼自己在不看代码、不看题解的情况下把思路重新“吐”出来。3.2 三遍法回看错题读题、复述、写关键一行我这次复习用的是一个很朴素但极有效的“三遍法”强烈推荐你也试试第一遍只看题号和题目背景不允许打开代码尝试回忆“这题考的是什么知识点、我当时卡在哪一步”。回忆得起来就过回忆不起来就标记成重点等会儿重点关注。第二遍用自己的话把完整解题思路说一遍假装面前有个队友。比如“这题先二分答案然后 check 函数里跑一遍 BFS看能不能把起点和终点连起来”说得出逻辑链路才算真懂。第三遍不看模板只写关键的一行或者一个转移方程。比如 Dijkstra 里的松弛条件、DP 的转移式、二分的mid取法把最核心的那一小段默写出来。因为真正让你记忆牢固的不是完整的几百行代码而是那一两个决定生死的逻辑判断。3.3 踩坑实例INF 设成 INT_MAX 导致的最短路 WA这次复习我翻到了一道老题当时我为了方便直接把INF定义成了INT_MAX然后 Dijkstra 的松弛就写得特别随意if (dist[v] w dist[u]) { dist[u] dist[v] w; }样例过了交上去 WA。我查了很久才找到原因当dist[v]等于INT_MAX时加上任意一个正数w直接就整数溢出变成负数于是dist[v] w dist[u]这个条件反而成立了一个不可达的点被错误地“更新”成了负数距离。这就是为什么我一直强调复习错题时要专门去看“常量定义”“边界条件”这种不起眼的地方。题目考的是算法不假但让你挂掉的往往不是算法本身而是这些底层细节。改成0x3f3f3f3f之后两个 INF 相加依然没有溢出这个坑就彻底堵死了。4. 把60分钟拆成时间盒9:13-10:13 的具体作战表复习最怕“什么都想复习最后什么都没复习”。所以这 60 分钟我拆成了四个时间盒每个盒子都有明确的产出物。节奏感一来人就特别容易进入状态。时间段时长复习内容产出物9:13-9:2512分钟错题速扫三张待深挖的卡片9:25-9:4520分钟核心模板复写三套手写模板9:45-10:0520分钟同类题限时自测两道题的自测结果10:05-10:138分钟记忆卡片输出三张记忆卡片4.1 12分钟错题速扫这 12 分钟我不深入做题只是把过去一周做错的题全部扫一遍。手里拿一支笔在题目旁边快速标注三类符号三角形代表“思路忘记”、圆圈代表“代码细节出错”、叉号代表“知识点完全没底”。扫题的核心是快速分类不要钻进去。我扫了大概 15 道错题其中 3 道标了三角形2 道标了圆圈没有叉号说明大框架还在但细节需要捞一下。4.2 20分钟模板复写接下来 20 分钟我从那 3 道三角形错题里提炼出三个核心模板堆优化的 Dijkstra、一个线性 DP 的骨架、一个二分答案的通用框架。要求自己不看任何资料用白纸当作代码编辑器一行一行默写。默写的结果是Dijkstra 的优先队列声明多写了个小括号二分的mid一开始写成了(l r) / 2DP 的初始化倒是写对了。这些错误如果在真实比赛里出现那就是 WA。复习阶段把它们抓出来总比比赛时红屏好得多。4.3 20分钟限时自测时间盒里的自测我通常选两道“做错过但已经改对”的题的细微变形限时 15 分钟一道。注意不是原题重做而是稍微改一下数据条件或者问法比如把“求最短路”改成“求次短路”、把数组范围加大看你会不会优化、把二分的上下界扣掉一个让你自己补。限时的意义在于模拟真实比赛的压力。你会发现很多平时“会”的题一限时就原形毕露——写代码变慢、边界想不清楚、调试浪费时间。但这些都是好事因为复习阶段的每一次暴露都是正式比赛前的一针疫苗。4.4 8分钟记忆卡片输出最后 8 分钟我把今天复习的核心内容浓缩成三张卡片每张卡片的格式是“问题 一句话提示 易错点”。比如其中一张写的是 “问题Dijkstra 的 INF 应该选多少提示0x3f3f3f3f能用 memset两个 INF 相加不溢出。易错点INT_MAX 做 INF 时加法会溢出成负数。”卡片写完之后我趁热把它们贴到手机备忘录里。碎片时间翻一遍比临时抱佛脚牢靠得多。5. 这套复习法在不同阶段的微调思路5.1 倒计时大于30天重体系轻单点如果你离比赛还有30天以上第39天这种“集中复习日”不用安排得太频繁一周一次就够。平时还是以学新专题为主但每周的错题扫速可以固定做一次。重点是把知识点织成网比如今天复习到 INF就顺手把“什么时候需要设置不可达状态”“还有哪些初始化技巧”一起回忆一遍让单点知识有机会连成体系。5.2 倒计时小于15天复习比重提到70%越临近比赛越不要追求“我还差哪个专题没刷”。这时候你最需要的不是知识增量而是把已有知识变成条件反射。我的建议是把时间盒拆得更碎早上一小时扫错题下午做一套模拟赛晚上只对错题做三遍法。新题可以完全不碰模拟赛里遇到的新知识点赛后当复习材料来处理而不是一头扎进去学两天。5.3 在校生打工人40分钟微缩版如果你白天有课或者上班凑不出完整一小时那可以把我的时间盒压成 40 分钟8 分钟错题扫描 15 分钟模板复写 15 分钟限时小测 2 分钟写三行卡片。别小看这 40 分钟只要固定时段、固定产出物复习效果并不会比一小时差多少。关键是“每天都有固定回看”而不是“攒到周末一次看一晚上”。我今天这一个小时下来最大的感受是复习不一定要掌握新东西有时候把旧东西确认到“随时能拿出来用”的程度比多刷几道题重要太多了。特别是 0x3f 这种细节如果不是这次专门回看最短路模板我可能一直停留在“用过但说不清为什么”的状态下次换个场景照样栽。所以如果你也正在备考不妨像我一样给自己的复习也打个精确到分钟的卡——比如明天 9:13 到 10:13就专门干这一件事只复习不刷新题。试试看你会发现之前那些“学过就忘”的毛病大部分都是因为没给记忆一个回头拉一把的机会。
返回列表