CSP-S 复赛第一天,选手小林和小周被分在同一间机房,同一道题。
题目是这样的:给定 n 个整数(n ≤ 10⁵),统计有多少对下标 (i, j) 满足 a[i] + a[j] = K。
小林扫了一眼数据范围,心里嘀咕:「n 才 10 万,也不算大嘛。」他手指飞快地敲下两重循环:外层 i 从 1 到 n,内层 j 从 1 到 n,相等就计数器加一。他粗略一算:10⁵ × 10⁵ = 10¹⁰ 次判断。「现在的电脑一秒能跑好几亿次,应该撑一撑就过了吧。」
小周却迟迟没有动笔。他先做了三件事:第一步,翻到题目末尾,确认时限是 1 秒;第二步,回忆老师说过的那句经验值 ——「1 秒钟,评测机大约能执行 10⁸ 次简单运算」;第三步,做除法:10¹⁰ ÷ 10⁸ = 100。
「需要 100 秒,而我只给 1 秒。」小周长舒一口气,幸好没有先写代码。他改用哈希表:读入每个 a[i] 的同时,查一下 K − a[i] 之前是否出现过;出现过就累加答案,然后把 a[i] 放进哈希表。总共大约 10⁵ 次操作,预计用时不到 0.05 秒。
出考场时小林问他:「你怎么知道 10¹⁰ 会超时?你又没跑过。」小周说了一句话,小林记了很久:
「我不知道我的程序会跑多少毫秒,但我知道它随 n 增长的速度是『平方级』。
n 到了 10⁵,平方就一定爆。」
这道题的差距不在代码能力,而在一个更靠前的能力 —— 在写代码之前,先算清楚这笔账。
💡 故事背后的算法思想
评测机的「预算」是有限的:常规约定 1 秒时限下,评测机大约执行 10⁸ 次简单运算(现代评测机可能在 3×10⁸ ~ 10⁹ 之间,但按 10⁸ 保守估算最安全)。这个数字就是你的总预算,任何算法都必须在这个预算内把活干完。
复杂度描述的是「增长量级」,不是「耗时」。O(n²) 和 O(n log n) 在 n = 10 时几乎没有差别;但在 n = 10⁵ 时,前者要跑 10¹⁰ 次,后者只要约 1.7×10⁶ 次 —— 差了近 6000 倍。数据规模越大,量级的差距越致命。
本讲要建立的核心能力:看到数据范围 → 反推出可接受的复杂度上限 → 在这个上限内挑算法。这是提高组和入门组最本质的分水岭:入门选手先想「怎么写」,提高组选手先想「能不能过」。
空间也是预算:常见限制 256MB 意味着最多约 6.4×10⁷ 个 int。开数组之前先乘一下,就不会出现「代码写对了却 MLE 零分」的悲剧。