RMQ区间最值查询 ST表笔记

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+2j1) 中的最大值。

递推边界(起始值)是 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} 2j1的子区间的最大值中较大的一个。
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,j1],F[i+2j1,j1])

这就是个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(1N)=113253342566477859910711
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+2j11 为一段, i + 2 j − 1 i+2^{j-1} i+2j1 i + 2 j − 1 i+2^{j-1} i+2j1 为另一段。
长度均为 2 j − 1 2^{j-1} 2j1 。当 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,j1],F[i+2j1,j1])

代码:

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 ji+1
所以可以取 k = l o g 2 ( j − i + 1 ) k=log_2(j-i+1) k=log2(ji+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[j2k+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(82+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[822+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[822+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));
	}
}

猜你喜欢

转载自blog.csdn.net/skyflying266/article/details/125358523
今日推荐