ST表本身就能实现严格O(1) RMQ查询,但需O(n log n)预处理时间和空间,且不支持修改;分块法以O(n)空间和预处理、近似O(1)查询(最坏O(√n))换取缓存友好性与内存效率,尤其适合超大静态数组。

为什么不能直接用ST表实现O(1) RMQ查询?
ST表(Sparse Table)确实支持O(1)查询,但预处理是O(n log n)空间,且不支持修改。当数据静态、内存敏感(比如嵌入式或超大数组)、或你只需要近似O(1)而非严格理论最优时,分块(Block-based RMQ)反而更实用——它把空间压到O(n),预处理O(n),查询均摊O(1)(最坏O(√n),但实践中常视为常数)。
关键点在于:分块不是为了吊打ST表,而是为权衡空间、构建速度与缓存友好性。尤其在n > 10⁷时,ST表的二维数组容易引发TLB miss,而分块的线性扫描+小数组查表更亲和CPU cache。
怎么划分块并预处理每块最大值?
设块大小B = ⌊√n⌋,共K = ⌈n / B⌉块。对每块i,预处理block_max[i]为其内部最大值;同时维护每个位置j的前缀最大值prefix_max[j](从块头到j)和后缀最大值suffix_max[j](从j到块尾)。
-
block_max[i]可单次遍历所有元素计算,O(n) -
prefix_max和suffix_max按块分别扫:每块O(B),总O(n) - 别用
sqrt(n)实时计算块号——改用整除:block_id = idx / B(确保B > 0) - 边界要检查:若
n % B != 0,最后一块长度不足B,访问suffix_max时需限定索引范围,否则越界
RMQ查询时如何合并跨块结果?
查询[L, R]时,分三种情况:
立即学习“C++免费学习笔记(深入)”;
- 若
L和R在同一块(即L/B == R/B),直接遍历[L, R],O(√n)最坏,但实际极短 - 否则,设左块
l_block = L/B + 1,右块r_block = R/B - 1;中间完整块的最大值用ST表或线段树查——但这里我们不用!直接用预处理好的block_max数组做区间[l_block, r_block]的RMQ,而这个区间本身只有O(√n)个块,所以再对block_max建一个**小型ST表**(仅K ≈ √n大小),查询就O(1) - 最后合并:左散点部分用
suffix_max[L],右散点部分用prefix_max[R],中间块用小型ST表结果,三者取max
注意:suffix_max[L]表示“从L到其所在块末尾的最大值”,不是整个后缀;同理prefix_max[R]只管到块开头。这正是它们能快速给出边界答案的原因。
哪些细节会导致WA或TLE?
实战中最常栽在边界和初始化上:
-
block_max数组长度必须是K,但分配时若写成new int[n/B]会漏掉最后一块——正确是int K = (n + B - 1) / B - 小型ST表的
st[0][i]必须严格初始化为block_max[i],不能复用原数组地址而忽略拷贝 - 查询时
L > R虽非法,但生产代码应加assert(L 或返回<code>INT_MIN,避免静默错误 - 使用
vector时,避免反复push_back导致多次重分配;优先reserve(K)再填值 - 编译时开
-O2,否则prefix_max和suffix_max的连续内存访问优势会被抵消
分块RMQ真正的复杂点不在逻辑,而在块大小B的选取与边界对齐——B=√n只是理论平衡点,实际中B=64或128(匹配cache line)往往更快,但这需要实测,不是公式能代替的。


















