某气象站保存了过去 n 天的日最高气温记录 a[1..n]。这些数据一旦入库就不再修改——历史的温度不会变。
现在系统要回答 q 次查询,每次形如:「第 l 天到第 r 天之间,最高气温是多少?」
程序员小赵的方案:每次查询就 for 一遍 a[l..r] 取最大值。代码三行,查询一次 O(r−l+1)。当 n = q = 105 且区间都很长时,总代价 1010——查询服务直接卡死。
工程师小钱的方案:他问了一个奇怪的问题——「如果我只预处理出「所有长度为 1、2、4、8、16……的区间的最大值」,够不够用?」
组里的人都笑了:「长度有 n(n+1)/2 种可能,你只算 log n 种,怎么可能够?」
小钱说:「够的。因为任意一个区间 [l, r],长度是 len,我取 k = ⌊log₂ len⌋,那么它一定可以被两个长度恰好为 2k 的区间「盖住」:一个从 l 开始,一个到 r 结束。这两个区间加起来比 [l, r] 长,所以中间会重叠一部分——但取最大值这个运算不怕重叠,重叠的部分算两次结果也一样。」
他当场演示了 [3, 7] 的查询:长度 5,k = ⌊log₂ 5⌋ = 2,于是取 max of 区间[3,6] 与 区间[4,7]。两个区间都长 4,加起来盖住了 [3,7],重叠了 [4,6]。答案一次比较就出来了。
预处理 O(n log n),查询 O(1)——这就是 ST 表(Sparse Table,稀疏表)。
💡 为什么「重叠不影响答案」是这个算法成立的关键
先看一个反例:如果把「求最大值」换成「求区间和」,同样的套路就彻底错了。因为重叠部分 [4,6] 会被加两次,结果凭空变大。
再看为什么最大值没问题:设答案区间的最大值是 M。拆出的两段合起来覆盖了整个 [l, r],且没有漏掉任何一个元素,所以两段的最大值中一定有一个是 M——没漏。同时两段的元素全部来自 [l, r] 内部,所以两段的最大值都 ≤ M——没多。于是 max(左段, 右段) = M,重叠毫无影响。
把这件事抽象出来:一个运算 f 如果能满足 f(x, x) = x(幂等),并且满足结合律与交换律,那么「用若干重叠区间覆盖目标区间」就不会影响答案。这类运算在竞赛里被称为「可重复贡献」的运算。
可重复贡献的运算有哪些?取最大(max)、取最小(min)、最大公约数(gcd)、最小公倍数(lcm)、按位与(&)、按位或(|)。
不满足的有哪些?求和(+)、计数、异或(xor)——它们的共同特点是「同一个元素算两次会变」。这类运算必须用树状数组 / 线段树,不能用 ST 表。