RMQ_ST算法

RMQ问题:

        RMQ即Range Minimum/Maximum Query,意为在一个数组中,给定一个区间,查询该区间内的最小值/最大值。这个问题大概有三种算法:

        一是暴力解法,即遍历这个区间,这种算法处理不了查询次数非常大的问题。

        二是线段树,这种方法可以处理存在区间更新的问题,详见:线段树

        第三种就是ST(Sparse Table)算法。ST算法是一种在线算法,在线表示用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。

ST算法的预处理:

        ST算法的预处理实质是动态规划,以最小值为例,假设数组a[5]为:5,2,7,9,4

        定义DP的数组为mn。mn[i][j]表示从下标 i 开始的长度为2^j的区间中的最小值。例如mn[1][1]表示从下标1开始的长度为2的区间,即a[1],a[2]中的最小值2。明白这个概念后我们开始推导递推式:

        首先找初始条件,我们发现,mn[i][0]表示从i开始的长度为1的区间中的最小值,即这个数本身,那么DP的初始条件就是:mn[i][0]=a[i]。然后求mn[i][j]时我们发现,将长度为2^j的这个区间分为两半(2^j肯定是偶数),左右子区间分别有他们自己的最小值,而这两个最小值中的较小值就是整段区间的最小值。由此可以写出递推式:

                                                   mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1])

        解释一下这个式子:mn[i][j-1]表示从i开始的长度为2^(j-1)的区间中的最小值,2^(j-1)正好是这段区间的前一半;而1<<j-1其实就是整个区间长度的一半(位运算符优先级低于+/-),所以i+(1<<j-1)就正好是区间后一半的起点,则mn[i+(1<<j-1)][]j-1]就表示区间后一半中的最小值。

        总结一下DP的过程:先求出所有长度为1的区间的最小值,然后根据这些最小值,求出所有长度为2的区间的最小值,再根据这些最小值,求出所有长度为4的区间的最小值……

        下面根据DP的过程来总结出预处理的伪代码:

void init(int n,int arr[])
{
    for(int i=1;i<=n;i++)
        DP[i][0]=arr[i];

    for(int j=1;j<20;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            DP[i][j]=min(DP[i][j-1],DP[i+(1<<j-1)][j-1]);
        }
    }
}

         初始条件就不解释了,下面的二重循环比较有意思。

        1、为啥j的循环要写在外面?因为根据我们上面总结的DP过程:先求出所有长度为1的,再求长度为2的,再求长度为4的,即我们首先要遍历区间长度,而j就是控制区间长度的变量,所以要将j的循环写在外面。

        2、i的循环条件是怎么设的?从数组mn的角度讲,mn[i][j]表示从下标i开始的长度为2^j的区间内的最小值。如果i后面的元素个数不足2^j,那么就无法求出mn[i][j],循环终止条件由此可得,也可以写成:i+(1<<j) <= n+1。

        3、j的循环条件怎么设?根据mn数组的概念,j表示长度为2^j的区间内的最小值,所以j应满足:2^j <= n,而2的20次方大约为100多万,所以j的取值其实很小,一般取20~25即可。

ST算法的查询过程:

        首先给出结论:设要查询的区间为(l,r),设k=log2(r-l+1)。那么区间内的最小值为:

                                                      min( mn[l][k] , mn[r-(1<<k)+1][k] )

        解释一下这个式子:mn[l][k]表示从下标为l开始长度为2^k的区间中的最小值,即区间[l,l+2^k-1]。mn[r-(1<<k)+1][k]表示从下标为r-(1<<k)+1,开始长度为2^k的区间中的最小值,即区间[r-(1<<<k)+1,r],所以现在我们只要证明l+2^k-1 >= r-2^k+1就可以保证这两个区间覆盖到了整个的[l,r]。

        证明过程如下:

        若l+2^k-1 >= r-2^k+1,则移项可得:2^k+2^k >= r-l+2,

        即2^(k+1) >= r-l+2,而k=log2(r-l+1),将k代入,

        所以上式等于:2*(r-l+1) >= r-l+2,可以解得r-l >= 0,所以r>=l为结论成立的条件,而r,l分别表示区间的终点和起点,一定满足r>=l,得证。

c++代码:

#include<iostream>
#include<cmath>
using namespace std;
const int N=100005;
int a[N];
int mn[N][25];
int n,q,l,r;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
        //DP的初始条件
		mn[i][0]=a[i];
	}
	for(int j=1;j<20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]);
	while(cin>>l>>r)
	{
		int k=(int)(log((double)(r-l+1))/log(2.0));
		cout<<min(mn[l][k],mn[r-(1<<k)+1][k])<<endl;
	}
	return 0;
}

 

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值