RMQ-ST 详细讲解 poj3264为例

版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77905678

首先介绍一下什么是RMQ。RMQ (Range Minimum/Maximum Query)问题是指求区间最值的问题,这个问题用线段树同样可以解决。

线段树解决方法

ST

ST算法是解决RMQ问题较优的算法,它是基于动态规划和位运算实现的。它可以通过O( nlogn )的预处理对每次查询在O(1)时间得出答案,是一个很快速的算法。

我们设dp[i][j]是区间[i,i+ 2j -1]的最值,对于该区间它总共有 2j 个数,所以dp[i][j]实际就是求i和i之后(包括i的) 2j 个数的最值。
所以当我们要求解dp[i][j]时我们可以把它分为两部分,每部分数的个数均为 2j1 ,即分为区间[i,i+ 2j1 -1]的最值(即dp[i][j-1])和区间[i+ 2j1 ,i+ 2j -1]的最值(即dp[i+(1<<(j-1))][j-1])。
这样我们就得出了状态转移方程
dp[i][j] = max/min(dp[i][j-1],dp[i+(1<<(j-1))][j-1])

当我们查询任意区间[l,r]的最值的时候,先计算出该区间有多少个数(len=r-l+1),然后我们把它分为两个区间(这两个区间可以重叠但必须包含[l,r]中所有数),这样我们直接取这两个区间的最值即可。
现在关键问题就是怎么分这两个区间,其实就是计算 2k <=len,这样我们就可以把它分成前 2k 个数和后 2k 个数,即区间[l,l+ 2k -1](即dp[l][k])和区间[r-(1<<(k))+1,r](即dp[r-(1<<(k))+1][k])。
所以ans=max/min(dp[l][k],dp[r-(1<<(k))+1][k])

poj3264

题意
给你n个数和q次询问,(n<=50000,q<=200000),每次询问区间[l,r]中最大值和最小值的差。

题解
就是裸题,直接维护最大值和最小值即可。

代码

//#include <bits/stdc++.h>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int maxn = 5e5+5;
int ma[maxn][20],mi[maxn][20];
int n,m;

void ST(int len)
{
    int k = (int)(log((double)len)/log(2.0));
    for(int j=1;j<=k;j++)
    {
        for(int i=1;i+(1<<j)-1<=len;i++)
        {
            ma[i][j] = max(ma[i][j-1],ma[i+(1<<(j-1))][j-1]);
            mi[i][j] = min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
        }
    }
}

int RMQ(int l,int r)
{
    int k = (int)(log((double)(r-l+1))/log(2.0));
    int ans1 = max(ma[l][k],ma[r-(1<<k)+1][k]);
    int ans2 = min(mi[l][k],mi[r-(1<<k)+1][k]);
    return ans1-ans2;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&ma[i][0]),mi[i][0]=ma[i][0];
    ST(n);
    int l,r;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&l,&r);
        printf("%d\n",RMQ(l,r));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/WilliamSun0122/article/details/77905678