421 字
1 分钟
ST 表(Sparse Table)
2026-06-03

ST 表(Sparse Table)#

用于可重复贡献问题的 RMQ,预处理 O(nlogn)O(n\log n),查询 O(1)O(1)


核心思想:倍增 + 重叠区间合并#

长度为 2k2^k 的区间最值,可由两个长度为 2k12^{k-1} 的子区间 O(1)O(1) 合并得到:

st[i][k]=max(st[i][k1], st[i+2k1][k1])\text{st}[i][k] = \max(\text{st}[i][k-1],\ \text{st}[i+2^{k-1}][k-1])

关键性质#

  • 区间可以重叠,不影响结果(因为是 max/min,不是求和)
  • 任意区间 [l,r][l, r] 可拆成两个长度为 2k2^k 的区间覆盖(k=log2(rl+1)k = \lfloor \log_2 (r-l+1) \rfloor

构建过程#

// stmax[start][k]:从 start 开始、长度 2^k 的区间最大值
// 1) 初始化:长度 1
for (int i = 1; i <= n; i++)
stmax[i][0] = a[i];
// 2) 倍增递推
for (int j = 1; j <= __lg(n); j++)
{
int half = 1 << (j - 1); // 子区间长度
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
stmax[i][j] = max(stmax[i][j - 1],
stmax[i + half][j - 1]);
}
}

查询过程#

int query(int l, int r)
{
int k = __lg(r - l + 1); // 区间长度的对数
return max(stmax[l][k],
stmax[r - (1 << k) + 1][k]); // ← +1 别忘了!
}

右区间起点要点:r - (1 << k) + 1

写法结果
r - (1<<k) + 1✅ 正确
r - (1<<k)❌ 漏掉位置 r
b - (1 << (__lg(len) + 1))❌ 优先级错,变成 1 << (k+1)

复杂度陷阱#

错误的构建方式—对每个长度独立扫描整个区间,不复用小结果:

// ❌ O(n²) 写法
for (int k = 1; k <= __lg(n); k++)
{
int len = 1 << k;
for (int i = 1; i + len - 1 <= n; i++)
{
int mx = a[i];
for (int j = 1; j < len; j++) // 扫描整个区间!
mx = max(mx, a[i + j]);
st[k][i] = mx;
}
}

总操作数 k=1lognn2kO(n2)\sum_{k=1}^{\log n} n \cdot 2^k \approx O(n^2)

始终利用 DP 的 O(1) 合并,不要独立求解每个区间。


计算 log2x\lfloor \log_2 x \rfloor 的方法对比#

方法耗时备注
__lg(x)极小(1~3 条指令)GCC/Clang 内建,映射到 BSR/LZCNT,推荐
31 - __builtin_clz(x)同上等价实现
预处理数组 lg[x]O(1) 内存访问可移植,标准 C++
std::log2(x)(浮点运算)有精度风险,不推荐

预处理对数数组的原理#

int lg[MAXN];
lg[1] = 0;
for (int i = 2; i < MAXN; i++)
lg[i] = lg[i / 2] + 1;

利用递推 log2i=log2(i/2)+1\lfloor \log_2 i \rfloor = \lfloor \log_2 (i/2) \rfloor + 1 在整数上成立,完全避开浮点运算。


完整模板#

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
int stmax[MAXN][20];
int n, q;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
stmax[i][0] = x;
}
int LOG = __lg(n);
for (int j = 1; j <= LOG; j++)
{
int half = 1 << (j - 1);
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
stmax[i][j] = max(stmax[i][j - 1],
stmax[i + half][j - 1]);
}
}
while (q--)
{
int l, r;
cin >> l >> r;
int k = __lg(r - l + 1);
int ans = max(stmax[l][k],
stmax[r - (1 << k) + 1][k]);
cout << ans << "\n";
}
return 0;
}

踩坑记录#

  • ==查询公式一定要 r - (1<<k) + 1,少一个 +1 就漏掉位置 r==
  • ==1 << k + 1 不是 (1<<k) + 1,而是 1 << (k+1),运算符优先级坑==
  • ==构建时第一维是位置、第二维是幂次,全程保持一致不乱==
  • ==__lg(n) 作为幂次上限,不是固定写死 19==
  • ==每次递归调两次 po() 会导致指数级重复计算→快速幂里也犯过这毛病==
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

ST 表(Sparse Table)
https://caoyue.xin/posts/sparse-table/
作者
Colton/曹越
发布于
2026-06-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录