线段树可以O(n)的时间建树,O(logn)的时间复杂度情况下查询区间最值,但是ST表利用空间换时间,可以在O(nlogn)的时间打表,O(1)的时间复杂度下静态查询区间最值。
当查询次数多且静态查询的情况下,选择ST表来做区间最值查询更合适。
预处理也是一个利用动态规划打表的过程。
①定状态:a[ i ] : 初始数组;f[ i ][ j ] :第 i 个数起连续的 2j 个数中的最大值;
②初始化:f[ i ][ 0 ] = a[ i ],即第 i 个数起,长度为 20的最大值为a[ i ]本身;
③状态转移方程:因为我们每次能把[i,j]分成两段 – [i, i + 2j-1-1] 与 [i + 2j-1,i + 2j-1]
所以其状态转移方程为f[i][j] = max(f[i][j-1], f[i+(1<<(j-1))][j-1]);
例如:3 5 4 7 6 2 3
f [ 1 ][ 1 ] = max(3,5) = 5;
f[ 1 ][ 2 ] = max(3 5 4 7) = 7;
代码:
//初始化
void init(int n)
{
for(int i = 1; i <= n; i++)
f[i][0] = a[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 1; i + (1<<j) - 1 <= n; i++)
f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
假如说查询区间[ l , r ];
根据max的性质,可以把[l,r]拆分成两个相重叠的区间(例如:查询2 3 4 5 6,就可以分成2 3 4 5和3 4 5 6这两个区间),每次找到覆盖这个闭区间的最小幂(左边界取 l,右边界取 r),区间长度len = r - l + 1,所以k = 2log(len)(即左右子区间的长度,且log(len)向下取整)
–> query(l,r) = max(f[ l ][ k ], f[ r - 2k + 1 ][ k ])
代码:
//查询区间l ~ r的最值(目前写的是最大值,最小值改为 min)
int query(int l,int r)
{
int k = 0;
while((1<<(k+1)) <= r - l + 1)
k++;
return max(f[l][k],f[r - (1<<k) + 1][k]);
}
//ST表求RMQ问题
#include
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 10;
const int INF = 0x3f3f3f3f;
const int N = 1e2 + 10;
int n,q,a[maxn],f[maxn][N]; //a[] -- 初始数值 f[][] -- 区间最值
//初始化
void init(int n)
{
for(int i = 1; i <= n; i++)
f[i][0] = a[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 1; i + (1<<j) - 1 <= n; i++)
f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
//查询区间l ~ r的最值(目前写的是最大值,最小值改为 min)
int query(int l,int r)
{
int k = 0;
while((1<<(k+1)) <= r - l + 1)
k++;
return max(f[l][k],f[r - (1<<k) + 1][k]);
}
int main()
{
//n个初始值,q次询问区间最值
scanf("%d%d",&n,&q);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
init(n);
for(int i = 1; i <= q; i++)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}
洛谷 P3865 【模板】ST表
其实就相当于模板题,只是因为题目特殊,时间只能到0.9秒,像我这样写的ST表第11个点会超时,所以加上了快读。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int n,m,a[maxn],f[maxn][50];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){
if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){
x=x*10+ch-48;ch=getchar();}
return x*f;
}
void init()
{
for(int i = 1; i <= n; i++)
f[i][0] = a[i];
for(int j = 1; (1<<j) <= n; j++)
for(int i = 1; i + (1<<j) - 1 <= n; i++)
f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int query(int l, int r)
{
int k = 0;
while((1 << (k+1)) <= r - l + 1)
k++;
return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
n = read(), m = read();
for(int i = 1; i <= n; i++)
a[i] = read();
init();
for(int i = 1; i <= m; i++)
{
int l,r;
l = read(), r = read();
printf("%d\n",query(l,r));
}
return 0;
}