CPH-第九章 区间查询
区间查询类问题,是后续高级数据结构与算法的重要基础。核心目标是在不同约束下高效处理以下问题:
- 区间求和
- 最值查询
- 单点更新
- 区间更新
9.1 静态数组查询
- 针对数组元素不修改、仅回答查询的场景,有两种针对性的高效方案。
前缀和数组(区间和查询)
- 适用场景:仅需计算区间和,无元素更新。
- 预处理:\(O(n)\) 时间构造前缀和数组,
prefix[k]表示原数组从开头到位置 k 的总和。 - 查询公式:任意区间 \([a, b]\) 的和可通过容斥在 \(O(1)\) 算出:
sum(a,b) = prefix[b] - prefix[a-1]。 - 二维扩展:二维前缀和可通过容斥公式 \(S(A)-S(B)-S(C)+S(D)\) 计算任意矩形子数组的和,同样支持 \(O(1)\) 查询。
稀疏表(Sparse Table,区间最值查询)
- 适用场景:静态数组的区间最小值 / 最大值 / gcd 查询 ,不支持元素修改。
- 预处理:\(O(n \log n)\) 时间,预计算所有长度为 \(2^k\) 的区间的最值。
- 查询原理:
- 对于任意区间 \([a, b]\),取最大的 \(k\) 使得 \(2^k\) ≤ 区间长度。
- 用两个长度为 \(2^k\) 的重叠区间覆盖目标区间,取两者最值即可。
- 查询时间 \(O(1)\)。
- 特点:是静态区间最值的最优实现,常数极小,效率极高。
9.2 树状数组(Binary Indexed Tree / Fenwick Tree)
- 定位:前缀和数组的动态升级版,支持单点更新 + 前缀和查询,两项操作均为 \(O(\log n)\)。
- 核心原理:
- 每个位置 k 对应一段区间,区间长度为
p(k) = k & -k(k 的二进制最低位 1 对应的数值)。 tree[k]存储原数组中以 \(k\) 结尾、长度为 \(p(k)\) 的区间和。
- 每个位置 k 对应一段区间,区间长度为
- 两个核心操作:
- 前缀和查询:将 \([1, k]\) 拆分为若干个树状数组区间,累加得到结果。
- 单点更新:修改位置 \(k\) 的值后,同步向上更新所有包含 \(k\) 的树状数组区间。
- 优势:代码极短、运行常数远小于线段树。
- 局限性:是仅支持前缀和类操作,无法直接处理区间最值。
9.3 线段树(Segment Tree)
- 定位:通用型区间数据结构,支持几乎所有可合并的区间查询(和、最值、gcd、位运算等)与单点更新,两项操作均为 \(O(\log n)\)。
- 结构设计:
- 本质是一棵二叉树,叶子节点对应数组单个元素,内部节点存储对应区间的聚合结果。
- 通常将数组长度补到 \(2\) 的幂,用大小为 \(2*n\) 的数组存储整棵树。
- 核心操作
- 区间查询:将目标区间拆分为 \(O(\log n)\) 个树节点,合并节点结果得到答案。
- 单点更新:修改叶子节点后,自底向上更新所有父节点的值。
- 区间更新:配合懒标记,区间加、区间赋值等操作同样可以在 \(O(\log n)\) 内完成
- 与树状数组对比
- 树状数组:仅擅长前缀和,代码简单、常数小。
- 线段树:通用性极强,支持所有可合并的区间操作,但内存占用更大、实现稍复杂。
9.4 附加技巧
离散化 / 索引压缩(Index Compression)
- 问题背景:当数组索引范围极大(如 \(10^9\))但实际用到的索引数量很少时,直接开数组会造成内存溢出。
- 解法:将所有用到的索引收集排序,映射为连续的 \(1,2,3,\cdots\) 编号,保持相对顺序不变,再在压缩后的索引上建立数据结构。
- 前提:必须提前知道所有会用到的索引,属于离线优化技巧。
区间更新
- 问题背景:需要对整个区间 \([a, b]\) 统一加上一个值,同时支持查询。
- 差分思想:构造差分数组,区间加 \(x\) 等价于在 \(a\) 处 \(+x\)、\(b + 1\) 处 \(-x\),仅需两次单点更新。
- 实现方式:用树状数组或线段树维护差分数组,即可高效支持区间更新 + 单点查询。
浙公网安备 33010602011771号