RMQ(区间最值问题)ST算法

 RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
 最先想到的是线性扫描,时间复杂度为O(n)-O(qn),即建立+扫描时间,但是针对于查询次数的数据量较大时,引入ST算法
 ST(实质是动态规划),O(nlogn)-O(q) online。

ST算法(Sparse Table),以求最大值为例,设d[i,j]表示[i,i+2^j-1]这个区间内的最大值,那么在询问到[a,b]区间的最大值时答案就是max(d[a,k], d[b-2^k+1,k]),其中k是满足2^k<=b-a+1(即长度)的最大的k,即k=[ln(b-a+1)/ln(2)]。
d的求法可以用动态规划,d[i, j]=max(d[i, j-1],d[i+2^(j-1), j-1])。
这里写图片描述
首先是预处理,用一个DP解决。设a是要求区间最值的数列,f[i,j]表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……从这里可以看出f[i,0]其实就等于a[i]。这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。我们把f[i,j](j≥1)平均分成两段(因为j≥1时,f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和6,8,1,2这两段。f就是这两段的最大值中的最大值。于是我们得到了动规方程F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。
接下来是得出最值,也许你想不到计算出f有什么用处,一般要想计算max还是要O(logn),甚至O(n)。但有一个很好的办法,做到了O(1)。还是分开来。如在上例中我们要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。扩展到一般情况,就是把区间[l,r]分成两个长度为2^n的区间(保证有f对应)。直接给出表达式:

k=trunc(ln(r-l+1)/ln(2));
ans=max(F[l,k],F[r-2^k+1,k]);

这样就计算了从l开始,长度为2^k的区间和从r-2^k+1开始长度为2^k的区间的最大值(表达式比较繁琐,细节问题如加1减1需要仔细考虑),二者中的较大者就是整个区间[l,r]上的最大值。

ST算法的核心就是预处理时间较长,查询为O(1),满足大规模查询问题】

void RQM(int num)
{
    //建立动态表
    for(int j=1;(1<<j)<=num;j++)
    {
        for(int i=0;i+(1<<j)-1<num;i++)
        {
            int m = i+(1<<(j-1));
            Max_dp[i][j] =max(Max_dp[i][j-1],Max_dp[m][j-1]);
            Min_dp[i][j] =min(Min_dp[i][j-1],Min_dp[m][j-1]);
        }   
    } 
}

上面的代码,j作为控制2^的几次方可以涵盖长度为num的数列,i作为可变的初始位置,即将2^j分为两份,来选取区间最大,满足动态转移方程,每一部分为2^(j-1)。初始值为dp[i][0]=A[i],保证长度为2^0时,最大最小值都是本身,建立动态初始状态。

查询:

int query(int l,int r)
{
    int len = r-l+1;
    //C语言中log默认以e为底,所有除以log2 
    int m = (int)(log((double)len) / log(2.0));
    int maxx = max(Max_dp[l][m],Max_dp[r-(1<<m)+1][m]); 
    int minn = min(Min_dp[l][m],Min_dp[r-(1<<m)+1][m]);
    return maxx-minn;
}

查询时,给出左右限界,2^m=len,m=log2len,C语言默认log函数时以e为底,所以用这样(int)(log((double)len) / log(2.0))处理
然后,给出l~2^m 和 r-2^m+1~2^m 两部分的最大值
举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);也就是2~4,5~9两个区间,涵盖[2,8]区间所有值。

结合例题就比较好理解了。
POJ:3264
Balanced Lineup
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 51886 Accepted: 24326
Case Time Limit: 2000MS

Description

For the daily milking, Farmer John’s N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.

Farmer John has made a list of Q (1 ≤ Q ≤ 200,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.

Input
Line 1: Two space-separated integers, N and Q.
Lines 2..N+1: Line i+1 contains a single integer that is the height of cow i
Lines N+2..N+Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.

Output
Lines 1..Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.

Sample Input

6 3
1
7
3
4
2
5
1 5
4 6
2 2

Sample Output

6
3
0
题意是,这个人要和牛玩游戏(也是他妈的闲的,跟谁玩不好),然后每个牛身高不一,他把牛排好队之后,希望做多次查询,查询每个区间内最高和最矮的两头牛的差值,事例一看就懂。标准的RMQ裸题,顺便总结一下模板

#include<stdio.h>
#include<algorithm>
#include<math.h>

using namespace std;

#define MAX 50005

int Max_dp[MAX][100];
int Min_dp[MAX][100];

void RQM(int num)
{
    //建立动态表
    for(int j=1;(1<<j)<=num;j++)
    {
        for(int i=0;i+(1<<j)-1<num;i++)
        {
            int m = i+(1<<(j-1));
            Max_dp[i][j] =max(Max_dp[i][j-1],Max_dp[m][j-1]);
            Min_dp[i][j] =min(Min_dp[i][j-1],Min_dp[m][j-1]);
        }   
    } 
}
int query(int l,int r)
{
    int len = r-l+1;
    //C语言中log默认以e为底,所有除以log2 
    int m = (int)(log((double)len) / log(2.0));
    int maxx = max(Max_dp[l][m],Max_dp[r-(1<<m)+1][m]); 
    int minn = min(Min_dp[l][m],Min_dp[r-(1<<m)+1][m]);
    return maxx-minn;
}
int main()
{
    int num,quer;
    int l,r;
    scanf("%d%d",&num,&quer);
    for(int i=0;i<num;i++)
    {
        scanf("%d",&Max_dp[i][0]);
        Min_dp[i][0] = Max_dp[i][0];//长度为2^0时为本身,作为DP的初始值       
    }
    RQM(num);
    for(int j=0;j<quer;j++)
    {
        scanf("%d%d",&l,&r);
        //题意中数组下标从1开始 
        l--;
        r--;
        int result = query(l,r);
        printf("%d\n",result);
    }
    return 0;
}

那么解RMQ还有更快速的方法,如线段树(O(n)-O(qlogn))LCA(O(n)-O(q)),后续学习,会作为博客总结。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值