ST表
S T ST ST 表用于解决 R M Q RMQ RMQ 问题,即给定一列数,每次询问区间 [ l , r ] [l,r] [l,r] 的最大/小值。
静态预处理
S T ST ST 表在使用前需要进行预处理,时间复杂度为 O ( n ) O(n) O(n) ,用倍增的思想,用一个数组 F i , j F_{i,j} Fi,j 表示以 i i i 为起点的长度为 2 j 2^j 2j 的区间的最值,用动态规划的思想来进行转移,即用两个小区间来表示大区间,以此我们得到状态转移方程为 F i , j = min ( F i , j − 1 , F i + 2 j − 1 , j − 1 ) F_{i, j} = \min(F_{i,j-1},\,F_{i + 2^{j-1},j-1}) Fi,j=min(Fi,j−1,Fi+2j−1,j−1) ,初始状态为 F i , 0 = A [ i ] F_{i,0} = A[i] Fi,0=A[i] 。
我们还可以预处理的是 l o g log log 数组,我们需要频繁的用到它(以 2 2 2 为底的对数),因此我们也可以进行一个预处理,其状态转移方程为 l o g i = l o g i / 2 + 1 log_{i} = log_{i/2} + 1 logi=logi/2+1 。
for (int i = 1; i <= n; i++)
ST[i][0] = A[i], Log[i] = Log[i >> 1] + 1;
for (int j = 1; j < 25; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
ST[i][j] = max(ST[i][j - 1], ST[i + (1 << j - 1)][j - 1]);
查询
进行预处理后,我们需要考虑如何用上处理好的值,我们需要找到两个区间,使其完全包含 [ l , r ] [l,r] [l,r] ,那么我们就可以对区间长度取 l o g log log ,这样我们就得到了两个小区间的长度为 2 k 2 ^ k 2k,之后只需要返回 min F i , k , F j − 2 k + 1 , k \min{F_{i, k},\,F_{j - 2^k + 1,k}} minFi,k,Fj−2k+1,k 即可。
l = read(), r = read();
int k = Log[r - l + 1];
printf("%d\n", max(ST[l][k], ST[r - (1 << k) + 1][k]));
我们可以发现,查询时两个小区间可能会有重叠部分,这对于最值来说并无影响,但是对于区间和一类的就会产生错误。
#include <bits/stdc++.h>
#define maxn 200005
using namespace std;
int Log[maxn], ST[maxn][25], A[maxn];
int n, m;
inline int read() {
int ret = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -f;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
ret = (ret << 1) + (ret << 3) + (ch ^ '0');
ch = getchar();
}
return ret * f;
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i++)
A[i] = read();
Log[0] = -1;
for (int i = 1; i <= n; i++)
ST[i][0] = A[i], Log[i] = Log[i >> 1] + 1;
for (int j = 1; j < 25; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
ST[i][j] = max(ST[i][j - 1], ST[i + (1 << j - 1)][j - 1]);
for (int i = 0, l, r; i < m; i++) {
l = read(), r = read();
int k = Log[r - l + 1];
printf("%d\n", max(ST[l][k], ST[r - (1 << k) + 1][k]));
}
return 0;
}