跳转至

ST表

简介

ST表(Sparse Table,稀疏表)是一种基于倍增思想的数据结构,可以在 \(\Theta(n\log n)\) 时间内完成预处理,之后以 \(\Theta(1)\) 的时间复杂度回答每个询问,但不支持修改操作。

ST表主要用于解决 可重复贡献问题,例如区间最大值、最小值和区间最大公约数(GCD)等问题。

可重复贡献问题 的定义

如果一个运算 \(opt\) 满足 \(opt(x, x) = x\),那么这个问题就是可重复贡献的,例如: - 最大值: \(\max(a, a) = a\) - 最小值: \(\min(a, a) = a\) - 按位与: \(a \& a = a\) - 按位或: \(a | a = a\) 这意味着同一个值对运算结果贡献多次也不会影响最终结果。这是ST表能够正确工作的基础。

实现

以区间最大值为例,我们来讲解ST表的具体实现。

根据倍增思想,我们定义 \(f_{i,j}\) 表示从原数列第 \(i\) 位开始,长度为 \(2^j\) 的区间内的最大值。显然,当 \(j=0\) 时,\(f_{i,0} = a_i\)(即区间长度为1时的最大值就是元素本身)。

通过这个定义,我们可以发现任意一个区间都可以由至多两个预处理好的区间进行覆盖。

具体来说,对于一个区间 \([l, r]\),我们可以将其分为两个可能重叠的子区间:

  • \([l, l + 2^{\lfloor \log_2(r-l+1) \rfloor} - 1]\)
  • \([r - 2^{\lfloor \log_2(r-l+1) \rfloor} + 1, r]\)

需要注意的是,这两个预处理出来的区间可能会有重叠部分,但根据上文中对于 可重复贡献问题 的定义,重叠部分并不影响答案的正确性。

数学表达如下:

\[\begin{align*} & \max(\boxed{\max(\{a_i, \dots, a_{i+2^{j-1}-1}\})}\boxed{\max(\{ a_{i+2^{j-1}},\dots a_{i+2^j-1}\}\})}) \\ = &\max(\boxed{\max(\{a_i, \dots, a_{i+2^j-1}\}}) \end{align*}\]

综上,我们可以写出ST表初始化和查询的代码:

typedef unsigned long long ull;

// 快速计算 log2(x) 的向下取整值
inline ull fast_log2(ull x) {return 63 - __builtin_clzll(x);}

// 初始化
int n, m;
cin >> n >> m;
for (int i = 0; i < n; ++i) cin >> a[i][0];
for (int j = 1; j <= logN; ++j)
  for (int i = 0; i + (1 << j) - 1 < n; ++i)
    a[i][j] = max(a[i][j-1], a[i + (1 << (j-1))][j-1]);

// 查询
while (m--) {
  int l, r;
  cin >> l >> r;
  int log_k = fast_log2(r - l + 1);
  printf("%d\n", max(a[l][log_k], a[r - (1 << log_k) + 1][log_k]));
}

一些老教材中可能会提前预处理log数组,但实际上这样做不仅预处理时间更慢,而且还需要耗费大量的内存空间。在竞赛中,一般直接使用cmath库中的log2()函数或者上述代码中定义的ull fast_log2()函数来实现。

在所有实现方法中,上述代码中的fast_log2()函数是最快的。该函数通过__builtin_clzll(x)统计x的前导零的数量,在最终编译时只会编译为单条CPU指令,全程在寄存器内完成操作,速度甚至快于读取L1缓存。

需要注意的是,该函数仅能处理\(x>0\)的情况,__builtin_clzll(0)没有定义,返回值可能是任意值。同时,这种方法只能得到\(\log_2 x\)向下取整的结果,如果你需要浮点数运算,则不能使用该方法。

最后在这里丢一个区间信息维护的算法一览,以便读者对于这类题目所使用的算法的功能特性有较清晰的认识。

数据结构 支持的查询/修改(典型) 单次复杂度(典型) 适用/备注 实现难度
树状数组(BIT) 点改、前缀和;两棵 BIT 做区间加+区间和 O(log n) 仅适合可加性信息(和/计数/秩);内存小、常数小
线段树(无懒标记) 区间和/最值/gcd 等查询;单点修改 O(log n) 通用“可合并信息”;不适合区间修改 低-中
线段树(懒标记) 区间加/赋值/翻转等 + 区间查询 O(log n) 在线区间修改首选;模板化强
吉司机线段树 区间 chmin/chmax/加/和/最值 均摊 O(log n) 复杂约束的区间操作;卡常敏感
主席树(可持久化线段树) 多版本点改 + 历史/当前版本查询(区间第 k/≤x 计数) O(log n) 离线/多版本;值域查询强 中-高
动态/可并线段树 大值域稀疏的点改/区间查;合并两棵树 O(log U) U 为值域;内存按需开点
2D 线段树 / 2D BIT 矩形修改/查询(和/计数/最值) O(log² n) 多维数据;实现较繁琐 中-高
树套树(线段树套平衡树/BIT) 区间内 ≤x 计数/第 k;点改 O(log² n) 顺序统计/值域统计;可扩展性强
隐式键平衡树(Treap/Splay) split/merge;区间翻转/加/赋值/求和/最大子段和 均摊 O(log n) 维护序列,支持复杂区间操作 中-高
重链剖分(HLD)+ 线段树/BIT 树上路径/子树 修改与查询 O(log² n)(可优到 O(log n)) 图为树;路径问题首选 中-高
LCT(Link-Cut Tree) 动态连边/断边;路径加/和/最值 均摊 O(log n) 动态树问题;实现难
Wavelet Tree(小波树) 区间第 k、≤x 计数;点更新 O(log σ) 值域相关顺序统计;σ 为值域
分块(根号分治) 区间加/赋值/和/最值(块标记) O((n/B)+B) 常数小、实现简;取 B≈√n
Sparse Table(ST 表) 静态 RMQ/GCD/Idempotent 查询 O(1) 查询,O(n log n) 预处理 只查不改;不可维护修改
莫队 / 带修莫队(离线) 大量区间统计;少量单点修改 约 O(q√n) / O((n+q)^{⅔}) 离线;适合频次/可加减贡献 中-高