引入
有时候我们需要静态查询一个序列上的区间最大 / 最小值问题,而它显然可以用线段树维护。
但是线段树太麻烦了,常数还大,更何况没有修改时用在这上面简直是杀鸡用牛刀……
所以有一种常数小得多的算法叫做
S
T
ST
ST 表
思路
我们用 s t j , i st_{j,i} stj,i 表示从序列中第 j j j 个数到第 j + 2 i − 1 j + 2^i - 1 j+2i−1 个数中的最大值(区间长度为 2 i 2^i 2i ,初始化 s t j , 0 = a j st_{j,0} = a_j stj,0=aj , a a a 为原序列),则运用倍增的思想将区间 ( j , j + 2 i − 1 ) (j,j + 2^i - 1) (j,j+2i−1) 由它的两个长度为 2 i − 1 2^{i-1} 2i−1 的不交的子区间合并而来,即 s t j , i = max ( s t j , i − 1 , s t j + 2 i − 1 , i − 1 ) st_{j,i} = \max(st_{j,i - 1},st_{j+2^{i-1},i - 1}) stj,i=max(stj,i−1,stj+2i−1,i−1) 。
那查询又该怎样处理呢?
假设要查询的区间为
(
x
,
y
)
(x,y)
(x,y) ,长度为
l
e
n
len
len ,记
k
=
⌊
ln
(
l
e
n
)
⌋
k = \lfloor\ln(len)\rfloor
k=⌊ln(len)⌋ ,则可以将区间视为一个从
x
x
x 到
x
+
2
k
x + 2^k
x+2k 的区间与一个从
y
−
2
k
+
1
y - 2^k + 1
y−2k+1 到
y
y
y 的区间的并。这两个区间肯定会有重叠,但是这不重要。首先,这两个字区间覆盖了整个原区间,同时也不会超出原区间。其次,区间
m
a
x
max
max 被多次统计得到的答案也不会有任何变化
实现
一个 模板 代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define rpt(i,a,n) for(int i = a;i <= n;++i)
const int N = 2e5 + 5,M = log2(N) + 5;
int st[N][M],lg[N],n,m,x,y;
inline int query(int x,int y){
int k = lg[y-x+1];//两个子区间的长度
int m = max(st[x][k],st[y-(1<<k)+1][k]);//合并两个子区间的信息
return m;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
rpt(i,2,n) lg[i] = lg[i>>1] + 1;//求 ln(i)
rep(i,n) cin>>st[i][0];//相当于给 st 表赋初值
rep(i,lg[n]) rep(j,n + 1 - (1 << i))
st[j][i] = max(st[j][i-1],st[j+(1<<(i-1))][i-1]);//初始化
while(m--){
cin>>x>>y;
cout<<query(x,y)<<'\n';
}
return 0;
}