浅谈ST表

原题是P3865

传送门

ST表是一种数据结构

ST表类似于树状数组和线段数的数据结构,能够快速访问一定区间内的最大值/最小值的值(解决RMQ问题:Range Minimum/Maximum Query,即区间最值查询)问题的离线算法。

其预处理的时间复杂度与线段树,树状数组相同,为O(n*logn)。

但ST表查询的时间复杂度为O(1),而线段树和树状数组查询的时间复杂度都为O(logn)。是不是更加优秀了呢!

首先我们来表示一下(以储存最大值为例):

void do_it(){
    for(int i=1;i<=n;i++) st[i][0]=a[i];
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
        }
    }
    return ;
}

首先我们需要说明一下,st[i][j]的意义表示从i开始向右数pow(2,j)位中的最大值

由于我们在内层循环中有判断j+(1<<i)-1<=n条件,所以一定会有i+pow(2,j)-1<=n

所以我们很容易得到st[i][j]一定是合法的范围内的

 

然后我们需要证明一个很简单的定理:

当我们将log2x向下取整时,有2log2 >x/2    ①

来证明一下:

因为我们将log2x向下取整,假定x==pow(2,n),则log2x==n

所以我们有当pow(2,n)<= x < pow(2,n+1)时的log2x均为n

所以就有2log2 == pow(2,n),因为易得有pow(2,n)>pow(2,n+1)/2

因为x<pow(2,n+1)

所以有2log2> x/2

QED.

 

然后我们现在还要考虑如何求得向下取整的log2x 这一问题

1.通过对数运算法则logab==logxb/logxa,以及调用cmath库中的log函数实现

x=log(b)/log(a)
(x是我们要求的logab)

优点:方便

缺点:当查询的次数很多时,很有可能使得时间复杂度大大增加

2.预处理得到范围内的所有logn

优点:访问方便且访问的时间复杂度为O(1)

缺点:访问次数很少是预处理会增加很大的时间复杂度

但是我们知道预处理的时间复杂度其实也是O(1)的,只不过只是一个很大的常数罢了,只要在范围内就不会TLE

 

那么我们如何范围内一定范围内的最大值呢?

假设我们要求i到x的最大值

因为我们已经有定理①,所以当我们要求i到i+pow(2,j)-1范围内的最大值时,我们只需要求i到i+pow(2,j-1)-1的最大值和x-pow(2,j-1)+1到x范围内的最大值并且取两者中的较大值就可以了!

AC代码:

#include<cctype>
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 100010;
int lg[N];
int n,m;
int a[N];
int st[N][20];
int l,r;
int read()
{
    int x = 1,a = 0;
    char ch = getchar();
    while(ch < '0' || ch > '9'){//如果读进来的不是数字……
        if(ch == '-')x = -1;//判断负号
        ch = getchar();
    }
    while(ch <= '9'&&ch >= '0'){//如果读进来的是数字……
        a = a * 10 + ch - '0';
        ch = getchar();
    }
    return x*a;
}
void init(){
    lg[1]=0;
    for(int i=2;i<=n;i++){
        lg[i]=lg[i/2]+1;
    }
    return ;
}
void do_it(){
    for(int i=1;i<=n;i++) st[i][0]=a[i];
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
        }
    }
    return ;
}
int main(){
    n=read();
    m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    init();
    do_it();
    for(int i=1;i<=m;i++){
        l=read();
        r=read();
        printf("%d\n",max(st[l][lg[r-l+1]],st[r-(1<<lg[r-l+1])+1][lg[r-l+1]]));
    }
    return 0;
}

 

posted @ 2020-09-18 19:28  Robertspot  阅读(324)  评论(0编辑  收藏  举报