ST表
引入:
给定一长度为 n n n的序列 n u m i num_i numi,有 Q Q Q次询问.对于每次询问,给定 l l l和 r r r,求区间 A l t o A r A_l to A_r AltoAr的最值.
这类无修改操作的静态区间最值问题称为 R M Q RMQ RMQ问题.不妨以求最大值为例,一种朴素的解法是,每次一一枚举区间 [ l , r ] [l,r] [l,r],逐个比较并更新区间最值.但是这种解法单次查询复杂度为 O ( n ) \Omicron(n) O(n),效率极低.为此我们引入 S T ST ST表
ST表:
S T ST ST一般用于解决静态运算可重复覆盖的问题.静态指无修改操作,运算可重复覆盖指参与运算的一个成员重复多次相同计算不会影响最终结果,类似于自反性.例如: m a x ( a , a ) = a max(a,a)=a max(a,a)=a.由于 R M Q RMQ RMQ问题恰好符合如上特征,因此 S T ST ST表十分适合用来解决此类问题.
S T ST ST表的核心思想在于倍增,优势是经过 O ( n l o g n ) \Omicron(nlogn) O(nlogn)的预处理,可以做到 O ( 1 ) \Omicron(1) O(1)查询,且常数极低.下面讨论 S T ST ST表的实现过程:
首先是建表.以 R M Q RMQ RMQ问题为例,我们建立数组 r m x [ i ] [ k ] rmx[i][k] rmx[i][k]表示从第 i i i个数开始向右(包含第 i i i个数) 2 k 2^k 2k个数的最大值.
对于 k = 0 k=0 k=0的情况,显然有 r m x [ i ] [ 0 ] = n u m [ i ] rmx[i][0]=num[i] rmx[i][0]=num[i]
k > 0 k>0 k>0时,由于 2 k = 2 k − 1 + 2 k − 1 2^k=2^{k-1}+2^{k-1} 2k=2k−1+2k−1,因此 r m x [ i ] [ k ] = m a x i ≤ j ≤ i + 2 k − 1 { n u m [ j ] } = m a x ( m a x i ≤ j ≤ i + 2 k − 1 − 1 { n u m j } , m a x i + 2 k − 1 ≤ j ≤ i + 2 k − 1 { n u m j } ) rmx[i][k]=max_{i\leq j\leq i+2^k-1}\{num[j] \}=max(max_{i\leq j\leq i+2^{k-1}-1 }\{num_j\} ,max_{i+2^{k-1}\leq j\leq i+2^k-1}\{num_j\}) rmx[i][k]=maxi≤j≤i+2k−1{num[j]}=max(maxi≤j≤i+2k−1−1{numj},maxi+2k−1≤j≤i+2k−1{numj})
因此转移方程可以写为 r m x [ i ] [ k ] = m a x ( r m x [ i ] [ k − 1 ] + r m x [ i + ( 1 < < ( k − 1 ) ) ] [ k − 1 ] ) rmx[i][k]=max(rmx[i][k-1]+rmx[i+(1<<(k-1))][k-1]) rmx[i][k]=max(rmx[i][k−1]+rmx[i+(1<<(k−1))][k−1])
预处理代码如下:
for (R ll i=1; i<=n; i++) mxr[i][0]=num[i];
for (R ll k=1; k<=20; k++) {
for (R ll i=1; i<=n; i++) {
mxr[i][k]=mxr[i][k-1];
if (i+(1<<(k-1))<=n) mxr[i][k]=max(mxr[i][k], mxr[i+(1<<(k-1))][k-1]);
}
}
按照相同方法倒序进行倍增,可以求出 l m x [ i ] [ k ] lmx[i][k] lmx[i][k].
对于每次询问给出区间 [ l , r ] [l,r] [l,r],区间长为 l e n = r − l + 1 len=r-l+1 len=r−l+1.我们只需要找到一个最大的 k k k,使得 2 k ≤ l e n 2^k\leq len 2k≤len,由于 m a x max max操作满足重复运算可覆盖性,因此答案显然就是 m a x ( r m x [ l ] [ k ] , l m x [ r ] [ k ] ) max(rmx[l][k], lmx[r][k]) max(rmx[l][k],lmx[r][k]) 由于每个 l e n len len对应一个特定的 k k k,因此只需要进行预处理求出即可.详见完整代码.
code
#define chkmax(x, y) (x=max(x, y))
const ll N=1e5+5;
ll n, m;
ll num[N];
ll lmx[N][20], rmx[N][20];
ll p[N];
int main() {
read(n); read(m);
for (R ll i=1, j=0; i<=n; i++) {
read(num[i]);
p[i]=j;
if (i==(1<<(j+1))) ++j;
lmx[i][0]=rmx[i][0]=num[i];
}
ll mx=p[n]+1;
for (R ll k=1; k<mx; k++) {
for (R ll i=1; i<=n; i++) {
rmx[i][k]=rmx[i][k-1];
if (i+(1<<k-1)<=n) chkmax(rmx[i][k], rmx[i+(1<<(k-1))][k-1]);
}
for (R ll i=n; i; i--) {
lmx[i][k]=lmx[i][k-1];
if (i-(1<<k-1)>0) chkmax(lmx[i][k], lmx[i-(1<<(k-1))][k-1]);
}
}
ll l, r, len;
while (m--) {
read(l); read(r); len=r-l+1;
writeln(max(rmx[l][p[len]], lmx[r][p[len]]));
}
}
扩展:
如果操作不满足重复运算可覆盖性,如加法运算,是否依然可以用 S T ST ST表处理呢?
答案是可以的,只不过查询复杂度会变为 O ( l o g n ) \Omicron(logn) O(logn)
求 [ l , r ] [l,r] [l,r]区间和,我们可以预处理出区间和 s u m [ i ] [ k ] sum[i][k] sum[i][k]表示区间 [ i , i + 2 k − 1 ] [i,i+2^k-1] [i,i+2k−1]的和.对于每次查询,区间长为 l e n = r − l + 1 len=r-l+1 len=r−l+1,可以将 l e n len len进行二进制拆分为 l e n = 2 k 1 + 2 k 2 + . . . + 2 k c len=2^{k_1}+2^{k_2}+...+2^{k_c} len=2k1+2k2+...+2kc,然后往右跳跃即可.参考下面代码理解一下:
inline ll get_sum(ll l, ll r) {
ll len=r-l+1, res=0;
for (R ll k=0; k<=20; k++) {
if (len>>k&1) res+=sum[l][k], l+=(1<<k);
}
return res;
}
这里只是介绍下倍增的跳跃性用法.当然,我们可以用 O ( n ) \Omicron(n) O(n)预处理前缀和 S [ n ] S[n] S[n],然后 O ( 1 ) \Omicron(1) O(1)查询 r e s = s u m [ r ] − s u m [ l − 1 ] res=sum[r]-sum[l-1] res=sum[r]−sum[l−1],这里的 S T ST ST表显然是大材小用了.