区间最值查询问题,即RMQ(Range Minimum/Maximum Query)

常见解法有朴素算法——O(n)预处理、O(n)查询,总体复杂度O(n+nq)

线段树——O(nlogn)预处理、O(logn)查询,总体复杂度O((n+q)logn)

ST算法——O(nlogn)预处理、O(1)查询,总体复杂度O(nlogn+q)




ST算法

ST算法是基于倍增算法与动态规划的一个解决静态区间RMQ的算法

忽略朴素算法,与线段树进行对比

优点是查询的时间复杂度为线性,对于查询多的题型有利

缺点是只支持对静态区间的查询(初始化后就不能修改值),且空间复杂度在大部分情况下比线段树要大(线段树保守情况为O(4n),ST算法为O(nlogn)




ST算法的实现

预处理部分

①:基于倍增算法,我们需要预处理从某个位置 i 开始往后 2 的次方倍长度区间内的最值并储存起来

②:假设此次查询的区间内共有 x 个元素,则需要返回 logx 向下取整,为保证算法O(1)实现,所以这一步也进行预处理储存


查询部分

虽然运用了倍增算法

很容易想到可以从查询的左边界 L 开始

每次将 log(R-L+1)向下取整 个长度的区间的最值取出来与答案进行取大(这里的最值就是预处理部分的①)

再更新左边界 L 继续取下去

【数据结构】ST算法 - 区间最值查询问题

【数据结构】ST算法 - 区间最值查询问题

【数据结构】ST算法 - 区间最值查询问题

(图示箭头即为每次L的更新过程)

也可以看成将R-L+1转成二进制后,根据1的位置来处理接下来要走多少步

但是这种做法显然是O(logn)级别的


实际上,既然已经预处理出来从某个位置 i 开始往后的 2 从次方倍长度区间中的最值

那我们就可以根据 log(R-L+1) 的值

从 L 开始往右取 2log 长度区间内的最值

再从 R 开始往左取 2log 长度区间内的最值

将这两个最值取大即为答案,时间复杂度为O(1)

可以保证的是,L 往右取的区间与R往左取的区间一定有重叠部分,且不会超过对方的位置

【数据结构】ST算法 - 区间最值查询问题

(图中两线段即代表取的区间)




ST算法的代码实现

代码中以求最大值为例,改为最小值只需要把max改成min即可

数据储存方式

const int MAXN=100050,MAXF=18;
int ar[MAXN];//原数组
int dp[MAXN][MAXF];//dp[i][j]表示从i开始往右2^j长度的区间内的最值
int LOG[MAXN];//LOG[i]=log2(i)向下取整

保证 2MAXF-1>MAXN,防止越界


预处理部分

对于dp数组

可以发现20=1,所以 dp[i][0] = ar[i] ,将第0层处理掉(如无其他条件,可以忽略ar数组直接读入dp数组)

从j=1开始,与其他问题的倍增算法类似,根据dp思想,由 2i = 2i-1 + 2i-1 可以得到状态转移方程为

dp[i][j] = max( dp[i][j-1] , dp[i+(1<<(j-1))][j-1] )

用语言描述就是

从 i 位置开始 2j 长度区间内的最大值,可以由 “从 i 位置开始 2j-1 长度区间内的最大值” ,“从 i+2j-1 位置开始 2j-1 长度区间内的最大值” 这两个值取大转移而来

for(int i=1;i<=n;i++)
    dp[i][0]=ar[i];
for(int j=1;(1<<j)<=n;j++)//如果长度已经超过n,则没有继续处理的必要
    for(int i=1;i+(1<<(j-1))<=n;i++)//如果从i开始往右没法取2^j长度的区间,则无法继续处理
        dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);

对于LOG数组

根据除法整除的性质,log数组可以通过

log[i] = log[i/2] + 1

转移而来 ( log[1] = 0 )

LOG[1]=0;
for(int i=2;i<=n;i++)
    LOG[i]=LOG[i/2]+1;

查询部分

输入查询区间 L 与 R

首先得到区间内元素个数为 R-L+1 ,对应的两端区间长度为 2LOG[R-L+1]

令d=LOG[R-L+1]

可以得到从L开始往右这段区间以 L 为左边界,则最大值为 dp[L][d]

从R开始往左取的这段区间左边界为 R-2d+1,则最大值为 dp[R-(1<<d)+1][d]

两个值取大输出即为答案

scanf("%d%d",&L,&R);
d=LOG[R-L+1];
printf("%d\n",max(dp[L][d],dp[R-(1<<d)+1][d]));



完整程序(模板)

模板例题:Luogu P3865

(这题如果出现TLE,尝试使用快读,题目已说明)

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

const int MAXN=100050,MAXF=18;
int ar[MAXN];
int dp[MAXN][MAXF];
int LOG[MAXN];

int main()
{
    int n,q,L,R,d;
    scanf("%d%d",&n,&q);
    
    LOG[1]=0;
    for(int i=2;i<=n;i++)
        LOG[i]=LOG[i/2]+1;
    
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&ar[i]);
        dp[i][0]=ar[i];
    }

    for(int j=1;(1<<j)<=n;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]);

    while(q--)
    {
        scanf("%d%d",&L,&R);
        d=LOG[R-L+1];
        printf("%d\n",max(dp[L][d],dp[R-(1<<d)+1][d]));
    }

    return 0;
}



参考了forever_dreams的CSDN博客81127189


分类:

技术点:

相关文章: