RMQ问题(ST算法)

RMQ(Range Maximum(Minimum) Query)问题,即询问某个区间内的最大值或最小值,这里主要涉及RMQ问题的求解方法——ST算法。

简介


ST算法通常用于多次询问一些区间的最值的问题中。我们知道线段树可以O(mlogn)的解决RMQ问题(m是查询次数)。但是如果m很大(一般1e6以上),但是n比较小,就可以用ST算法解决RMQ问题,O(nlogn)预处理+O(1)查询。

算法流程


1.预处理
ST算法的原理实际上是动态规划,我们用a[1…n]表示一组数。设f[i][j]表示从a[i]到a[i+2 j-1]这个范围内的最大值,也就是以a[i]为起点连续2 j个数的最大值。由于元素的个数为2 j个,所以从中间平均分成两部分,每一部分的元素个数恰好为2 j-1个,也就是说,把f[i][j]分为f[i][j-1]和f[i+2 j-1][j-1]。
在这里插入图片描述
整个区间的最大值一定是左右两部分最大值的较大值,满足动态规划的最优化原理。转移方程为,f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1])。边界条件为f[i][0]=a[i]。这样就能在O(nlogn)的时间复杂度内预处理f数组。
2.询问
若我们要询问区间[l,r]的最大值,则先求出最大的x满足2 x<=r-l+1,那么区间[l,r]=[l,l+2 x-1]U[r-2 x+1,r]。
在这里插入图片描述

这里简单证明一下,这两个区间的并集就是[l,r]。首先此时2x<=r-l+1,所以l+2x-1<=r,r-2x+1>=l。所以不会比[l,r]大。那么会不会比[l,r]小内。利用反证法,我们这里假设比[l,r]小,那么r-2x+1>l+2x-1,我们移项,发现r-l+1>2x+1-1因为2x是小于等于r-l+1的最大的x,那么2x+1肯定大于r-l+1,所以r-l+1<=2x+1-1,与之前结论矛盾。那么就证明出来了。两区间并集就是[l,r]。
两区间的元素个数都为2

技巧


因为cmath库中的log2函数效率不高,所以除了调用log2函数外,通常还会使用O(n)递推预处理出1~n这n中区间长度各自对应的s值。具体地,log[d]表示log 2d向下取整,则log[d]=log[d/2]+1。

例题


数列区间最大值
模板题,直接上代码。

//ST算法O(nlogn)解决区间查询最大最小值问题(不能修改)
//cmath库自带log2函数效率不高,可以O(n)递推预处理log数组,之后可以O(1)查询
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
const int LogN=20;
int f[maxn][LogN+5];
int log[maxn];
int a[maxn];
int n,m,x,y;
int main()
{
    scanf("%d%d",&n,&m);
    log[0]=-1;//log[0]是-1才能使log[1]是0
    for(int i=1;i<=n;++i)
    scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)//O(n)预处理log数组
    log[i]=log[i>>1]+1;
    for(int i=1;i<=n;++i)
    f[i][0]=a[i];
    for(int j=1;j<=LogN;++j)
    for(int i=1;i+(1<<j)-1<=n;++i)//边界不能超过n
    f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
    while(m--)
    {
        scanf("%d%d",&x,&y);
        int s=log[y-x+1];//求log(y-x+1)向下取整的值
        printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s]));
    }
    return 0;
}

最敏捷的机器人
也是模板题,只不过既要保存最大值,也要保存最小值。

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int LogN=20;
const int maxn=1e6+5;
int n,k;
int log[maxn];
int f1[maxn][LogN+5];
int f2[maxn][LogN+5];
int a[maxn];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)
    scanf("%d",&a[i]);
    log[0]=-1;
    for(int i=1;i<=n;++i)
    {
        f1[i][0]=a[i];
        f2[i][0]=a[i];
        log[i]=log[i>>1]+1;
    }
    for(int j=1;j<=LogN;++j)
    for(int i=1;i+(1<<j)-1<=n;++i)
    {
        f1[i][j]=max(f1[i][j-1],f1[i+(1<<j-1)][j-1]);
        f2[i][j]=min(f2[i][j-1],f2[i+(1<<j-1)][j-1]);
    }
    for(int i=1;i+k-1<=n;++i)
    {
        int s=log[k];
        printf("%d %d\n",max(f1[i][s],f1[i+k-1-(1<<s)+1][s]),min(f2[i][s],f2[i+k-1-(1<<s)+1][s]));
    }
    return 0;
}

与众不同
比较难的题目,我们可以O(n)推出last[],st[],f[]。last[value]表示value的之前最后出现位置。st[i]表示以i为结尾的最长完美序列的起点,st[i]=max(st[i-1],last[value]+1)。f[i]表示以i为结尾的最长完美序列长度,f[i]=i-st[i]+1。然后O(nlogn)求出mx[]。我们发现st[]是非递减的。那么一个区间可以分成两部分,前面部分的起点在l之前,后面部分的起点在l之后。然后由于是非递减的,就可以利用二分O(log)的求出分界点m。这样前面就是m-l。后面就是RMQ问题,max(f[m][s],f[r-(1<<s)+1][s])。

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;
const int maxm=2e6+5;
const int LogN=20;
int n,m,x,y;
int a[maxn],last[maxm],st[maxn],f[maxn],mx[maxn][LogN];
int log[maxn];
int query(int l,int r)
{
    if(st[l]==l)
    return l;
    if(st[r]<l)
    return r+1;
    int ans=r+1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(st[mid]>=x)
        {
            r=mid-1;
            ans=min(ans,mid);
        }
        else
        {
            l=mid+1;
        }
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    scanf("%d",&a[i]);
    log[0]=-1;
    for(int i=1;i<=n;++i)
    {
        st[i]=max(st[i-1],last[a[i]+1000000]+1);
        f[i]=i-st[i]+1;
        last[a[i]+1000000]=i;
        mx[i][0]=f[i];
        log[i]=log[i>>1]+1;
    }
    for(int j=1;j<=LogN;++j)
    for(int i=1;i+(1<<j)-1<=n;++i)
    mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]);
    /*for(int i=1;i<=n;++i)
    cout<<st[i]<<" ";
    cout<<endl;
    */
    int ans;
    while(m--)
    {
        ans=0;
        scanf("%d%d",&x,&y);
        x++;
        y++;
        int m=query(x,y);
        //cout<<m<<endl;
        if(m>x)
        ans=m-x;
        if(m<=y)
        {
            int s=log[y-m+1];
            ans=max(ans,max(mx[m][s],mx[y-(1<<s)+1][s]));
        }
        printf("%d\n",ans);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值