ST算法处理RMQ

ST算法

​  ST算法是基于倍增原理的,常用于求解最值区间查询(Range Minimum/Maximum Query,所以也成为RMQ问题),这是一种离线算法

1.算法原理

​  ST算法其实完全可以属于动态规划一类,因为它完全符合动态规划的最优子结构,即一个问题的最优解可以通过其子问题的最优解组合得到
​  以最大值查询为例,一个大区间,如果能够被两个小区间覆盖,那么这个大区间的最值,一定等于这两个小区间的最值(无论最大值还是最小值),比如,有大区间{4,7,9,6,3,6},这个大区间完全被两个小区间{4,7,9,6,3},{6,3,6}覆盖,那么大区间的最大值就是两个小区间的最大值max(9,6)=9,并且不难看出,两个小区间的部分重合不影响效果,只需要保证两个小区间的并集完全等于大区间即可

2.实现步骤

  • 预处理小区间

​   关于离线问题,一般都会先预处理,ST表预处理包括两个步骤:

​   (1) 把整个数列分为若干小区间,并且提前计算出每个小区间的最值

​   (2) 对任意区间最值查询,找到覆盖它的两个小区间,并由两个小区间的最值算出答案

小区间的划分可以借助倍增原理,即对数列每个元素,把从它开始的数列分为长度为1,2,4,8,16,32……的小区间

​  并且由算法原理可知,每组小区间的最值,可以由前一组递推而来,那么就可以定义dp[i][j]表示左端点为 i ,区间长度为 2k 的区间的最值,那么预处理的递推关系式为dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][k-1]),每个数字为左端点的各种长度都需要计算,那么一个数字需要计算 log2n 次,有 n 个数据,那么时间复杂度为 O(nlog2n)

  • 查询任意区间最值

以任意元素为起点,后面有长度为1,2,4……的小区间,以任意元素为终点,前面亦有长度为1,2,4……的小区间

​  由此结论,可以将需要查询的区间 [L,R],分为两个小区间,一个是以L为起点的小区间,一个是以为R为重点的小区间,那么如何保证两个小区间的并集恰好等于查询区间呢?
​  区间 [L,R]的长度为 len=R-L+1,那么令两个小区间的长度均为 x,那么由此得到限制 x<=len,又因为我们预处理的数组中,用2的倍数来表示长度,所以 log2x <= log2len,由此性质我们可得,仅需要满足x为 比len小的2的最大倍数即可,所以 x=log2len,因为大量查询下,使用c++自带的log函数代码性能会显著下降,为此我们可以提前预处理log函数,

prelog[0]=-1;//这里要记得初始化,别忘了log函数在x趋近于0时,值趋近于负无穷
for(int i=1;i<=n;i++)
    prelog[i]=prelog[i>>1]+1;

​  综上所述,可得区间查询公式dp[L][R]=max(dp[L][x],dp[R-(1<<x)+1][x]),单次查询时间复杂度为 O(1)

3.模板代码(P3865 【模板】ST 表 && RMQ 问题 - 洛谷

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

ll n,m;
ll prelog[100005];
ll dp[100005][25];
void init(){
    prelog[0]=-1;
    for(int i=1;i<=n;i++)
        prelog[i]=prelog[i>>1]+1;
    ll len=prelog[n];
    for(int j=1;j<=len;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
ll query(ll l,ll r){
    ll len=r-l+1;
    len=prelog[len];
    return max(dp[l][len],dp[r-(1<<len)+1][len]);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>dp[i][0];
    
    init();

    while(m--){
        ll l,r;
        cin>>l>>r;
        cout<<query(l,r)<<"\n";//此处小优化,endl每次输出会清空缓冲区,多次输出时效率会降低,所以此处采用 '\n'提高代码性能
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值