ST表
用途:N个数,M次询问,每次给定区间[L, R],求区间最大值
(不支持在线修改,是一种离线算法)
要求:O(1) 时间内求区间最大值
设原数组为 a[]
方案:利用倍增思想,倍数为2。
令f(i, j)表示区间[i, i+
2
j
2^j
2j],即下标 i 开始的
2
j
2^j
2j 个数组成的区间。
由定义得:f(i, 0) = a[i]; 这个边界条件
一般的转移:把一个区间分成两半
f
(
i
,
j
)
=
m
a
x
(
f
(
i
,
j
−
1
)
,
f
(
i
+
2
j
−
1
,
j
−
1
)
)
f(i, j) = max(f(i, j-1), f(i+2^{j-1}, j-1))
f(i,j)=max(f(i,j−1),f(i+2j−1,j−1))
(1)预处理:
算出所有f(i, j),i 和 a.size() 有关系,j 最大为log(a.size()),因此时间复杂度为O(nlogn)。存储结构用二维数组st[i][j],根据ij的范围,空间复杂度也是O(nlogn)。
for(int i = 0; i < n; ++i)
st[i][0] = a[i];
for(int j = 1; (1 << j) <= n; ++j){
for(int i = 0; i+(1 << j) <= n; ++i)
st[i][j] = max(st[i][j-1], st[i+(1 << (j-1))][j-1]);
}// st表的初始化
预处理的顺序为列先算,从左到右。
原因:st[i][j] = max(st[i][j-1], st[i+(1 << (j-1))][j-1]);
由递推式看出,算某个元素,需要已知它上一列同一行的元素,以及上一列的下面某一行的元素。例:上图 橙色 = max(绿色, 蓝色);
(2) 查询:
初始化时,每一个状态对应的区间长度都为2的整数次幂(
2
j
2^j
2j),由于给出的查询区间长度不一定恰好为
2
j
2^j
2j。
因此我们可以求两个重叠的区间的最值,即求出了这个区间的最值,具体如下:
查询[L, R]区间最值,则求[L, k],[R-k+1, R]的最值,而这两个区间必须要重叠,因此要满足k > (L+R)/2。
如何确定k?我们发现
2
l
o
g
(
l
e
n
)
>
l
e
n
/
2
2log(len) > len/2
2log(len)>len/2,len为区间长度。而恰好
2
l
o
g
(
l
e
n
)
2log(len)
2log(len)的长度满足作为st表f(i, j)的区间长度的条件,因此
2
l
o
g
(
l
e
n
)
2log(len)
2log(len)可作为k值。接下来证明
2
l
o
g
(
l
e
n
>
l
e
n
/
2
2log(len > len/2
2log(len>len/2:
向下取整
[
2
l
o
g
(
l
e
n
)
」
<
=
l
e
n
[2log(len)」<= len
[2log(len)」<=len,当len刚好是2的整数幂时,不等式取等,
l
e
n
>
l
e
n
/
2
len > len/2
len>len/2自然成立;取小于号的时候,设
[
2
l
o
g
(
l
e
n
)
」
=
=
l
e
n
2
<
l
e
n
,
[2log(len)」== len2 < len,
[2log(len)」==len2<len,即
2
l
o
g
(
l
e
n
2
)
=
=
l
e
n
2
2log(len2)== len2
2log(len2)==len2,此时len2是比len小的最大的一个2的整数幂。写成二进制:
Len:000010…010…0
Len2: 000010…0
Len2是2的幂次,某一位为1,len比len2稍大一些,该位也是1,并且在右边也会有若干个1,len/2相当于右移1位,发现
l
e
n
/
2
<
l
e
n
2
len/2 < len2
len/2<len2,因此
[
2
l
o
g
(
l
e
n
)
=
=
l
e
n
2
>
l
e
n
/
2
[2log(len)== len2 > len/2
[2log(len)==len2>len/2
int search(int l, int r){
int k = (int)(log((double)(r - l + 1)) / log(2.0));// 换底公式,将底换成2
return max(st[l][k],st[r - (1 << k) + 1][k]);
}