RMQ算法理解

RMQ(Range Minimum/Maximum Query),即区间最值查询。RMQ算法一般用较长时间做预处理,时间复杂度为O(nlogn),然后可以在O(1)的时间内处理每次查询。RMQ算法讲解

我们设二维数组dp[i][j]表示从第i位开始连续2j个数中的最小值。

我们假设数组arr为:1,2,6,8,4,3,7
那么dp[2][1]就表示从第二位数开始连续两个数的最小值(也就是从第二位数到第三位数的最小值),即2,6中的最小值,所以dp[2][1] = 2;另外dp[i][0]就表示第i个数字本身

RMQ的思想:

其实感觉RMQ有点像区间dp。它每次由两个长度为1的区间得到一个长度为2的区间的最值、再由两个长度为2的区间得到一个长度为4的区间的最值、再由两个长度为4的区间得到一个长度为8的区间的最值……这就是预处理的部分,处理时枚举所有区间长度和区间的起点,时间复杂度为O(nlogn)。

转移方程:dp[i][j] = min(dp [i][j - 1], dp [i + (1 << j - 1)][j - 1])

然后RMQ的思想也就决定了它代码中两层循环的先后顺序:它需要先将所有短的区间先处理完,再处理长的区间

//预处理
for(int i=1; i<=n; i++)
{
    scanf("%d",&h);
    dp_max[i][0]=h;//初始化
}
for(int j=1; j<=20; j++)
    for(int i=1; i+((1<<j)-1)<=n; i++)
        dp_max[i][j]=max(dp_max[i][j-1],dp_max[i+(1<<(j-1))][j-1]);
        		  //维护[i,i+2^(j-1)-1]    维护[i+2^(j-1),i+2^j-1]

RMQ的查询部分:

它的查询实际上也是由两个区间得来的,而且因为区间最值的特殊性,查询的这两个区间允许有重叠的部分,比如下图中的这个大区间[l, r]的最大值就等于图中两个小区间的最大值再取一个最大值。
在这里插入图片描述
在查询区间[l, r]的过程中,我们把l作为一个区间的左端点,r作为另一个区间的右端点,然后如果我们能够保证两个区间的长度大于或等于区间[l, r]长度的一半,就能够得到区间[l, r]的最值

这里我们取k=log2(r-l+1),2^k为两个小区间的长度。比如查询长度为10的区间,就取k=3,2^k=8;查询长度为16的区间,就取k=4,2^k=16;查询长度为31的区间,就取k=4,2^k=16。(可以看出2^k永远大于r-l+1/2)

//查询
int query(int l,int r)
{
    int k=log2(r-l+1);
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}

ps:从RMQ的预处理部分中可以知道,我们处理的区间的长度都是2的幂,虽然没有处理完所有的区间,但是最后我们可以通过两个允许重叠的区间得到所查询区间解,比如max[5,10]=max(max[5,8],max[7,10])。但如果要维护区间和之类的话,RMQ就不适用了。

例题:http://poj.org/problem?id=3264

代码:

#include <iostream>
#include <iostream>
#include <string.h>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
typedef long long LL;
using namespace std;
const int manx=1e5+10;
const int INF=0x3f3f3f3f;
int n,q,h,dp_min[manx][50],dp_max[manx][50],k,s,e;
int main()
{
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&h);
            dp_min[i][0]=h;
            dp_max[i][0]=h;
        }
        for(int j=1; j<=20; j++)
            for(int i=1; i+((1<<j)-1)<=n; i++)
            {
                dp_max[i][j]=max(dp_max[i][j-1],dp_max[i+(1<<(j-1))][j-1]);
                dp_min[i][j]=min(dp_min[i][j-1],dp_min[i+(1<<(j-1))][j-1]);
            }
        for(int i=1; i<=q; i++)
        {
            scanf("%d%d",&s,&e);
            k=log2(e-s+1);
            int mmin=min(dp_min[s][k],dp_min[e-(1<<k)+1][k]);
            int mmax=max(dp_max[s][k],dp_max[e-(1<<k)+1][k]);
            printf("%d\n",mmax-mmin);
        }
    }
}
附:

log2()用子函数预处理的写法:
//math.h中log2()复杂度是O(log n)

//直接搬学长代码了,嘿嘿
int LOG[maxN];
void pre()
{
    for(int i = 1, j = 0, nex = 2; i<maxN; i++)
    {
        if(i == nex)
        {
            nex <<= 1;  
            j++;
        }
        LOG[i] = j;
    }
}
发布了55 篇原创文章 · 获赞 36 · 访问量 6698

猜你喜欢

转载自blog.csdn.net/qq_43803508/article/details/104071450