ST 表是什么
简单来说,ST 表就是一个二维数组,使用这个数组来存储需要的信息。
ST 表能干什么
ST 表主要用来处理静态区间的 RMQ 问题。
其他可用 ST 表处理的问题:
- 最大公因数(gcd)
- 最小公倍数(lcm)
- 可重复贡献问题
- ⋯
所谓 RMQ 问题指的是区间的最大 / 最小值查询(Range Maximum/Minimum Query)。
朴素的 RMQ 问题算法时间复杂度为 O(n2),而使用 ST 表可以做到 O(nlogn) 预处理、O(1) 查询。
怎么使用 ST 表
二维数组的开辟
由于 ST 表使用了倍增的思想,所以创建的二维数组十分 “有精神”
1
| int32_t st[MAX_N][(int32_t)std::log2(MAX_N) + 1];
|
含义:st[i][j]
表示在区间 [i,i+2j) 中的最大值 / 最小值。
即:
sti,j=RMQ(i,i+2j−1)
由于 sti,0 表示 RMQ(i,i+20−1),所以可以将原序列存入 sti,0
预处理
code(以最大值为例):
1 2 3 4 5
| for (int32_t p = 1; p <= (int32_t)std::log2(MAX_N); ++p) { for (int32_t i = 1; i + (1 << p) <= n + 1; ++i) { st[i][p] = std::max(st[i][p - 1], st[i + (1 << (p - 1))][p - 1]); } }
|
由于我们计算倍增时需要使用到上一级计算的结果,所以将 p
放在最外层循环。
为了计算 sti,p,需要知道 sti,p−1 与 sti+2p−1,p−1
他们之间的关系如下(以 st1,2 为例):
st1,2=max(st1,1,st3,1)
st1,2s1s2st1,1s3s4st3,1s5⋯sn
这样是不是就很显然了?
查询
当我们需要查询 RMQ(l,r) 时,需要寻找两个区间 [l1,l1+2k1) 与 [l2,l2+2k2) ,使得这两个区间的并集刚好为 [l,r]
为什么是并集?为什么中间不能断开或者超过 [l,r]?
- 并集是因为在 stl1,l1+2k1 中保存了最值,而在 stl2,l2+2k2 中也保存了最值,所以将他们取并集刚好能够满足 [l,r] 时只需要对这两个值再取最值即可。
- 不能断开因为如果 [l,r] 中间有一部分为包含在并集内就无法判断整个 [l,r] 内的最值。
- 不能超过 [l,r] 是因为一旦并集包含了 [l,r] 以外的区域,那么这个并集内的最值同样为整个区域的最值而不是 [l,r] 内的最值。
所以我们需要
l1←ll2+2k2←r+1
在此基础上使 l1+2k1 尽量靠近 r,使 l2 尽量靠近 l。
由于预处理 ST 表时已经处理出所有的 sti,k,k∈[0,log2MAX_N]。所以我们直接将 l1 设为 l,现在只需要尽量使得 l1+2k1 靠近 r 即可。
那么可以使用对数计算这个区间的长度,即
k1=log2(r−l+1)k2=k1
log 是啥?
我们已经学过幂运算,形如 ab,那么如果我们知道幂运算的结果和底数如何知道指数呢?
没错,通过对数运算,我们就可以知道指数了。
logaab=b,a=0
code:
1 2 3 4
| int32_t query(const int32_t l, const int32_t r) { int32_t k = std::log2(r - l + 1); return max(st[l][k], st[r - (1 << k) + 1][k]); }
|
最后仍然是亿些练习
其实基本上滑动窗口 / 单调队列的题都可使用 ST 表(时间允许的情况下)
- 洛谷 P3865 【模板】ST 表
- 洛谷 P2880 [USACO07JAN] Balanced Lineup G
- 洛谷 P1816 忠诚
- 洛谷 P1886 滑动窗口 /【模板】单调队列