自制RMQ模板

RMQ

文章原创,转载请标明出处

(Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题

ST算法(百度百科)

来看一下ST算法是怎么实现的(以最大值为例):

首先是预处理一个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]上的最大值。

自己编的模板题

/***RMQ-自己编的模板题***/
//by 智慧之神 
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,a[1001],f[10001][15],k,l,r,res;
//n<=10000    2^14=16384
//f[i][j]中,i=n+1。j=以2为底i的对数 
double temp;
int main()
{
    scanf("%d%d",&n,&m);//n个数,m个询问 
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);//读入n个数 
        f[i][0]=a[i];
        //从i开始往后2的0次方个数,这里就是i了 
    }
    temp=log(n)/log(2.0);
    //以2为底,n的对数。为了确保范围不超n 
    for (int j=1;j<=temp;j++)//循环2的指数 
    {
        for (int i=1;i<=n;i++)//循环第几个数 
        {
            //f[i][j]表示:从i开始,向后2的j次方个数中的最大值 
            if (i+(1<<j)-1<=n)//如果没有超过n 
            {
                f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
                // 图示如下: 
                // i\___________max__________/ i+2^(j)
                // i\_____max____/i+2^(j-1)
                //     i+2^(j-1)\_____max____/ i+2^(j)
            }
        }
    }
    /*调试*/
//  for (int i=1;i<=n;i++)
//  {
//      for (int j=0;j<=temp;j++)
//      {
//          printf("%d ",f[i][j]);
//      }
//      printf("\n");
//  }
    for (int i=1;i<=m;i++)//m条询问 
    {
        scanf("%d%d",&l,&r);//读入左、右边界 
        k=log(r-l+1)/log(2);//确定2的指数范围 
        res=max(f[l][k],f[r-(1<<k)+1][k]);
        // 图示如下: 
        // l\___________max__________/ r
        // l\_____max______/l+2^k
        //       r-2^k\______max_____/ r
        //即:[l,l+2^k] ∪ [r-2^k,r] = [l,r] 
        printf("%d\n",res);//输出结果 
    }
    return 0;
}
/*
演示数据: 
5 7
1 2 3 4 5
1 2
2 3
1 3
1 5
2 5
3 5
2 4

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值