RMQ区间最值查询(Range Minimum/Maximum Query)
一般使用倍增思想或线段树 来实现。
用于解决长序列中最值多次查询的情况。RMQ算法一般用O(NlogN)的时间来处理数列,然后每次查询只需O(1)的时间。
这里使用大名鼎鼎的 S T 表 \color{hotpink}{ST表} ST表来实现,给定长度为N的数列A,ST表可以在O(NlogN)时间预处理后以O(1)的时间复杂度来回答任意区间内的最值。
预处理环节
设 F [ i , j ] F[i,j] F[i,j] 表示在数列A从第 i i i 个数起连续 2 j 2^j 2j 个数 ( i , i + 2 j − 1 ) (i,i+2^j-1) (i,i+2j−1) 中的最大值。
递推边界(起始值)是 F [ i , 0 ] = A [ i ] F[i,0]=A[i] F[i,0]=A[i] ,即数列A在区间 [ i , i ] [i,i] [i,i] 的最大值。
递推时把子区间的长度倍增,即长度为 2 j 2^j 2j 的子区间最大值为左右两半长度 2 j − 1 2^{j-1} 2j−1的子区间的最大值中较大的一个。
即 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])。
这就是个dp!
举例:
数 组 A ( 下 标 1 − N ) = { 1 1 , 3 2 , 5 3 , 3 4 , 2 5 , 6 6 , 4 7 , 7 8 , 5 9 , 9 10 , 7 11 } 数组A(下标1-N)={1_1,3_2,5_3,3_4,2_5,6_6,4_7,7_8,5_9,9_{10},7_{11}} 数组A(下标1−N)={11,32,53,34,25,66,47,78,59,910,711}
F [ 1 , 0 ] = A [ 2 0 ] = 1 , 按 上 面 的 递 推 式 : F[1,0]=A[2^0]=1,按上面的递推式: F[1,0]=A[20]=1,按上面的递推式:
F [ 1 , 1 ] = M A X ( 1 , 3 ) = 3 F[1,1]=MAX(1,3)=3 F[1,1]=MAX(1,3)=3
F [ 1 , 2 ] = M A X ( 1 , 3 , 5 , 3 ) = 5 F[1,2]=MAX(1,3,5,3)=5 F[1,2]=MAX(1,3,5,3)=5
F [ 1 , 3 ] = M A X ( 1 , 3 , 5 , 3 , 2 , 6 , 4 , 7 ) = 7 F[1,3]=MAX(1,3,5,3,2,6,4,7)=7 F[1,3]=MAX(1,3,5,3,2,6,4,7)=7
… … …… ……
我们把 F [ i . j ] F[i.j] F[i.j] 平均分为两段(因为区间内一共有偶数个数字),
从 i i i 到 i + 2 j − 1 − 1 i+2^{j-1}-1 i+2j−1−1 为一段, i + 2 j − 1 i+2^{j-1} i+2j−1 到 i + 2 j − 1 i+2^{j-1} i+2j−1 为另一段。
长度均为 2 j − 1 2^{j-1} 2j−1 。当 i = 1 , j = 3 i=1,j=3 i=1,j=3 时就是 1 , 3 , 5 , 3 1,3,5,3 1,3,5,3 和 2 , 6 , 4 , 7 2,6,4,7 2,6,4,7 这两段。
F [ i , j ] F[i,j] F[i,j] 保存的就是这两段中最大值的最大值。
即 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])。
代码:
void RMQ(int x){
//预处理
for(int j=1;j<10;j++)//j的范围根据题目设置
for(int i=1;i<=x;i++)
if(i+(1<<j)-1<=x){
maxx[i][j]=max(maxx[i][j-1],maxx[i+(1<<(j-1))][j-1]);
minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
}
}
查询环节
假设我们需要查询的区间为 [ i , j ] [i,j] [i,j] ,那么需要找到覆盖的闭区间(左闭右闭)。
可以有重叠部分,求最值时重叠不影响结果。
因为这个区间长度为 j − i + 1 j-i+1 j−i+1 ,
所以可以取 k = l o g 2 ( j − i + 1 ) k=log_2(j-i+1) k=log2(j−i+1) ,
则有 F [ i , j ] = M A X ( F [ i , k ] , F [ j − 2 k + 1 , k ] ) F[i,j]=MAX(F[i,k],F[j-2^k+1,k]) F[i,j]=MAX(F[i,k],F[j−2k+1,k]) 。
举例:
求区间 [ 2 , 8 ] [2,8] [2,8] 的最大值,
k = l o g 2 ( 8 − 2 + 1 ) = 2 k=log_2(8-2+1)=2 k=log2(8−2+1)=2 ,
M A X ( F [ 2 , 2 ] , F [ 8 − 2 2 + 1 , 2 ] ) MAX(F[2,2],F[8-2^2+1,2]) MAX(F[2,2],F[8−22+1,2])
= M A X ( F [ 2 , 2 ] , F [ 8 − 2 2 + 1 , 2 ] ) =MAX(F[2,2],F[8-2^2+1,2]) =MAX(F[2,2],F[8−22+1,2])
= M A X ( F [ 2 , 2 ] , F [ 5 , 2 ] ) =MAX(F[2,2],F[5,2]) =MAX(F[2,2],F[5,2])
代码:
#include<cmath>//记得log是cmath库里的函数
int query(int l,int r){
//查询
int k=log(r-l+1)/log(2);//换底公式把自然对数loge换成log2
return max(F[l][k],F[r-(1<<k)+1][k]);
}
做道例题试一试:模板ST表
code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int F[2000001][31];//2e6是2的21次方,开到31就可以覆盖int了
void RMQ(int x) {
//预处理
for (int j = 1; j < 31; j++) //j的范围根据题目设置
for (int i = 1; i + (1 << j) - 1 <= x; i++)
F[i][j] = max(F[i][j - 1], F[i + (1 << (j - 1))][j - 1]);
}
int query(int l, int r) {
//查询
int k = log(r - l + 1) / log(2); //换底公式把自然对数loge换成log2
return max(F[l][k], F[r - (1 << k) + 1][k]);
}
int n, m;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)scanf("%d", &F[i][0]);
RMQ(n);
for(int i=1,l,r;i<=m;i++){
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
}