ST表的实现利用了倍增思想,用于处理可重复贡献问题,例如区间最大值、区间最小值、区间最大公因数等(x opt x = x)问题。
RMQ模板问题
题目描述
给定一个长度为 N 的数列,和 M 次询问,求出每一次询问的区间内数字的最大值。
输入格式
第一行包含两个整数 N,M,分别表示数列的长度和询问的个数。
第二行包含 N 个整数(记为 ai),依次表示数列的第 i 项。
接下来 M 行,每行包含两个整数 li,ri,表示查询的区间为[li,ri]。
输出格式
输出包含 M 行,每行一个整数,依次表示每一次询问的结果。
输入 #1
8 8 9 3 1 7 5 6 0 8 1 6 1 5 2 7 2 6 1 8 4 8 3 7 1 8
输出 #1
9 9 7 7 9 8 7 9
说明/提示
对于 30% 的数据,满足 1≤N,M≤10。
对于 70% 的数据,满足1≤N,M≤105。
对于 100% 的数据,满足 1≤N≤105,1≤M≤2×106,ai∈[0,109],1≤li≤ri≤N。
实现思路
利用倍增思想,通过2的指数进行倍增,maxn[ i ][ j ] 表示 从 i 开始,2^j 个数字中的最大值,即[ i ,2^j ] 中的最大值
由于2的指数幂的特性,maxn[ i ][ j ] 可以拆分为 maxn[ i ][ j - 1] 和 maxn[ i+ 2^(j-1)][ j - 1] 两个区间,大区间的最大值,等于小区间最大值里最大的那个数
查询时,我们希望找到查询区间里被预处理过的最长的区间,即 l + 2^k -1 == r 的k值,计算log2( r - l +1)向下取整则为最长区间,区间长度小于等于查询区间,所以我们在右侧找一个对称的区间,两个区间必定全部包含查询区间
log2(n)一直用stl容易超时,需要进行预处理,等于1<<t 时+1即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read(){ //快读
char ch=getchar();
int f=1,x=0;
while(ch>'9'||ch<'0'){
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f*x;
}
int n,m;
int maxn[100005][30]; //RMQ
int Log[100005]; //预处理2的对数
void init(){ //预处理
Log[0]=-1;
for(int i=1;i<=100000;i++){
if((i&(i-1))==0)Log[i]=Log[i-1]+1; //判断 i 是不是1左移的结果
else Log[i]=Log[i-1];
}
for(int j=1;j<=Log[n];j++){ //外层循环是y,内层循环是x
for(int i=1;i+(1<<j)-1<=n;i++){ //注意边界
maxn[i][j]=max(maxn[i][j-1],maxn[i+(1<<(j-1))][j-1]); //分成两部分
}
}
}
int q(int l,int r){ //查询
int k=Log[r-l+1]; //分两个区间。先查询再合并
return max(maxn[l][k],maxn[r-(1<<k)+1][k]);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
maxn[i][0]=read(); //直接把maxn[i][0]读进去
}
init();
while(m--){
int l=read(),r=read();
printf("%d\n",q(l,r)); //O(1)查询
}
return 0;
}