RMQ问题---ST算法

一.什么是RMQ问题

RMQ(Range Minimum/Maximum Query),即区间最值查询问题。给你一组数据若干个查询,在短时间内回答每个查询[ l ,r ] 内的最值。

二.RMQ问题解决策略

(1)朴素搜索 : 暴力(DFS/BFS/搜索)

(2)线段树  :  线段树浅谈

(3)ST动态规划算法 : 本文(动态规划思想)

(4)笛卡尔树标准算法 : 待补

三.ST动态规划算法

1.什么是ST算法

  这里介绍了一种比较高效的在线算法(ST算法)解决这个问题。所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

2. 算法思想

(1)预处理

<1>这里应用了一个很重要的思想:我要求[ l, r ]里面的最值,我只要取[ l , x ] 中的最值和 [ x +1 , r ]中的最值中较大/小的一个就是 [ l,r ] 里面的最值,这就是动态规划的思想。

<2>我们用F[ i , j ]表示从i开始,连续(2^j)个数字中的最值。

这里为什么用2^j呢?

因为一方面我们要把区间等分,那么区间长度肯定是为偶数。

另一方面,2^x可以组合为任何长度。 

<3>由(1)可知:我们要把 2^j 等分 , 所以F[i , j] 对应区间为 [ i , i + 2^j - 1 ];等分后为 [ i, i + 2^(j-1) - 1] 和 [ i + 2^(j-1) , i + 2^j - 1 ];两区间内数字个数都为 2^(j-1)个,所以:

初态:F[ i ,0] = A[i] ;

递推公式: [i , j] = max( [ i , 2^(j-1) ] ,[ i + 2^(j-1) , 2^(j-1) ] ) 即 F[i , j] = max( F[ i , j-1 ] ,F[ i + 2^(j-1) , j-1 ] )

(2)查询

我们要查询[ l ,r ] 内的最值怎么办呢?因为区间长度(r - l + 1)可能并不正好是2^n,所以我们这里可以重复取最值。

比如: 查询[ 5 , 6 , 8 , 1, 10] 也就是 查询 max( [ 5 , 6 , 8 ,1] , [ 6 , 8 , 1 , 10]),可以重复!避免遗漏。

取k = log2(r - l + 1);  F[l,r] = max( F[ l , k] , F[ r - 2^k + 1 , k ] )

法一:int k = (int)(log(r - l + 1.0)/log(2.0));

法二:int k = 0; while((1<<(k+1))<=(r - l + 1))k++;

3.代码(查询区间最大/最小)

#include <iostream>
#include<bits/stdc++.h>
const int maxn = 100000 + 7;
using namespace std;
int Max[maxn][20],Min[maxn][20];
int n,m;
void init(){
     for(int j = 1;(1<<j)<=n;j++){
        for(int i = 1;i + (1<<j) - 1<=n;i++){
            Max[i][j] = max(Max[i][j-1],Max[i + (1<<(j-1))][j-1]);
            Min[i][j] = min(Min[i][j-1],Min[i + (1<<(j-1))][j-1]);
        }
     }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i = 1;i<=n;i++){
            int num;
            scanf("%d",&num);
            Max[i][0] = Min[i][0] = num;
        }
        init();
        while(m--){
            int l,r;
            scanf("%d%d",&l,&r);
            int k = (int)(log(r - l + 1.0)/log(2.0));//法一
//            int k = 0;
//            while((1<<(k+1))<=(r - l + 1))k++;//法二
            int ans1 = max(Max[l][k],Max[r - (1<<k) + 1][k]);
            int ans2 = min(Min[l][k],Min[r - (1<<k) + 1][k]);
            printf("%d %d\n",ans1,ans2);
        }
    }
    return 0;
}
//3
//10 3
//1 5 6 0 1000 -12 66 -100 300 99

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/83627372