分块 RMQ 不适合高频单点更新场景,因其单点更新需重算整块 left_max 和 right_max,最坏耗时 O(√n),写多读少时性能劣于线段树或稀疏表。

为什么分块 RMQ 不适合高频单点更新场景
分块 RMQ 的核心优势在于预处理后支持 O(1) 查询,但代价是修改任一元素时,可能影响多个块的预处理结果。一旦发生单点更新,你就得重新计算该元素所属块的整个 left_max 和 right_max 数组——最坏情况是整块重算,耗时 O(√n)。如果业务中写多读少(比如实时传感器数据流持续修正),分块反而比线段树或稀疏表更慢。
build_block() 预处理必须拆解为左右两个方向扫描
不能只扫一遍填满块内最大值数组;必须分别构建 left_max[i](从块左端到位置 i 的最大值)和 right_max[i](从块右端到位置 i 的最大值)。这是为了后续跨块查询时能用 O(1) 拼接中间块+左右边缘:
- 若查询区间完全落在一个块内,直接查
left_max[r]和right_max[l]的交集(实际取max(left_max[r], right_max[l])) - 若跨越多个块,左侧残缺部分用
right_max[l],右侧残缺部分用left_max[r],中间完整块则查预先存好的块级最大值block_max[k]
漏掉任一方向扫描,跨块查询就会出错或退化为线性扫描。
查询时判断「是否同块」比调用 sqrt() 更快且更稳
别在每次 query(l, r) 里反复算 l / block_size 和 r / block_size 再比较——浮点 sqrt() 可能引入精度误差,除法也非零开销。实操中应:
- 用整数除法:定义
block_id(i) = i >> shift(当block_size是 2 的幂时),或直接i / block_size(C++ 整除安全) - 先算
lid = block_id(l),rid = block_id(r),再用if (lid == rid)快速分流 - 避免任何
std::sqrt(n)调用——block_size应在初始化时固定为static_cast,之后全程用整数运算(std::sqrt(n)) + 1
内存布局不连续会导致缓存失效,block_max 必须单独连续分配
很多人把 left_max、right_max、block_max 全部塞进一个大 vector,靠偏移访问。这会让 block_max[k] 分散在内存各处,CPU 缓存预取失败,查中间块时性能骤降。正确做法是:
-
block_max单独用vector连续分配block_max(num_blocks) -
left_max和right_max可共用一个vector,但必须保证每块内部连续(即按块组织,而非全局索引)aux(2 * n) - 实测显示:
block_max若未连续,跨块查询延迟可上升 40% 以上(尤其在n > 1e6时)
分块 RMQ 的高效,本质是用空间局部性换时间——一旦打破这个前提,O(1) 查询就只是理论值。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











