高级数据结构(2)、ST表

时隔多年,我又来更新了233

昨天听雨神讲了一发差分/树上差分的问题,并且提到了LCA的求法,于是今天先决定更一发RMQ问题,然后过两天再更LCA的问题QAQ

RMQ(Range Minimum/Maximum Query),即区间最值查询问题。该问题描述的是在一个数组上,如何快速地求出给定区间[ L , R ]的最值的问题。例如对于给定数组[ 3 , 2 , 4 , 5 , 6 , 8 , 1 , 2 , 9 , 7],查询区间      [ 1 , 3 ]的最大值就是第三个数4,最小值就是第二个数2.

对于该问题,我们最先想到的是O(n) 的算法,即直接遍历一遍所给区间[ L , R ],即可求得最大值与最小值。但是如果这样的查询很频繁,造成的时间复杂度会很大,是我们难以接受的。于是我们需要想一个在线的算法,使得我们能够在线查询所给区间的最值。

我们先分析一手,为什么我们连续查询会造成比较大的复杂度。例如现在我们要查询[ 1 , 3 ]这个区间的最小值,可以求得是2;然后我们查询[ 2 , 5 ]和[ 1 , 4 ]的最小值,可以看到答案都是2,但是我们发现我们在这个过程中做了很多重复的工作。实际上,我们查询[ 1 , 4 ]的时候,只要查询min( [ 1 , 3 ] 和 [ 4 , 5 ] )就可以了,[ 1 , 3 ]的过程我们重复查询了一遍,这样就造成了额外的开销。那我们能不能先预处理整个数组,得到我们所需的全部信息,然后实现O(1)的查询呢?

这时候我们可以联想到动态规划的思想。动态规划是为了解决大量重复子问题而提出的,我们可以借助这个思想,来对我们的数组进行预处理,即区间dp。既然要用到动态规划,我们就要思考一下状态是什么。

在这里,我们使用了一个倍增的思想。即我们每次在区间增长的时候,并不是给区间+1,而是给区间*2。至于为什么要倍增,是因为倍增的话可以让每一次操作的复杂度由n降低为logn。所以我们就可以定义dp[i][j]为从第i个点开始,长度为2^j的区间的最大/最小值。显然,dp[i][0]代表的就是数组中每个元素对应的位置的最值,即该元素本身。

dp的状态和边界都找到了,这时候我们就要考虑一下状态的转移了。在这儿,我们找到的每一段区间必然都是偶数长度的(因为是2^j),那我们就可以把它拆成2个相同长度的子区间,每个子区间的长度为2^(j-1)。原区间的最值就等于这两个子区间的最值的最值。以最小值为例,dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1])

至此,我们已经预处理好了原数组,接下来就是查询了。但是有个小小的问题,即我们在预处理数组的时候,存储最值的区间长度都是2的j次方,而待查询的区间不一定正好是2^j,所以在查询的时候,我们依旧要对于待查区间进行预处理,方法如下。首先,我们将待查区间分成长度几乎相等的两个子区间,例如我们查询[ 4 , 12 ]这个区间,就分成[ 4 , 8 ]和[ 9 , 12 ]这两个区间,长度分别为5和4,然后扩大这两个区间,使得它们恰好为2的幂。这儿4就可以不动了,然后5就可以扩大成8。这儿需要注意的是,扩大后的两个区间是有可能重叠的,但是这并不会影响我们答案的正确性。这样我们就能O(1)地计算出这两个区间的最值的最值了。下面贴代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+7;
int stmax[maxn][30];
int stmin[maxn][30];
int a[maxn];

void rmq_st(int n){//预处理过程
    for(int i=1;i<=n;i++)
        stmax[i][0]=stmin[i][0]=a[i];//初始化
    int m=(int)(double(log(n))/log(2.0));//换底公式
    for(int j=1;j<=m;j++)//这儿切记,这两个循环顺序是不能颠倒的!
    for(int i=1;i+(1<<j)-1<=n;i++){
        stmax[i][j]=max(stmax[i][j-1],stmax[i+(1<<j-1)][j-1]);
        stmin[i][j]=min(stmin[i][j-1],stmin[i+(1<<j-1)][j-1]);
    }
}

void rmq_query(int l,int r){
    int k=(int)((double)log(r-l+1)/log(2.0));
    cout<<"Max is : "<<max(stmax[l][k],stmax[r-(1<<k)+1][k])<<endl;
    cout<<"Min is : "<<min(stmin[l][k],stmin[r-(1<<k)+1][k])<<endl;
}

int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    rmq_st(n);
    int l,r;
    while(cin>>l>>r){
        rmq_query(l,r);
    }
    return 0;
}


阅读更多
文章标签: ST表 RMQ
个人分类: 算法
上一篇图论(1)、最短路径
下一篇POJ-2823 Sliding Window
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭