RMQ的ST算法

RMQ 即 range maximum/minimum query

  1、朴素(即搜索),O(n)-O(qn) online。

  2、线段树,O(n)-O(qlogn) online。

  3、ST(实质是动态规划),O(nlogn)-O(1) 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])。

  4、RMQ标准算法:先规约成LCA(Lowest Common Ancestor),再规约成约束RMQ,O(n)-O(1) online。

 

  首先根据原数列,建立笛卡尔树,从而将问题在线性时间内规约为LCA问题。LCA问题可以在线性时间内规约为约束RMQ,也就是数列中任意两个相邻的数的差都是+1或-1的RMQ问题。约束RMQ有O(n)-O(1)的在线解法,故整个算法的时间复杂度为O(n)-O(1)。

 

 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。这样,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]上的最大值。

应用:

JOJ 2660 河床

时间上明显没有直接单调队列维护最大最小快,可能是运算log上浪费了时间。

对log优化了下,直接位运算取ln2稍微快了些


#include <cstdio>
#include <cmath>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)

const int maxn=30005;
const double ln2=log(2.0);

int dpmax[maxn][16],dpmin[maxn][16],w[maxn];
int n;

int getln2 (int x)
{
    int i;
    for(i=0 ; x ; ++i,x>>=1);
    return i-1;
}

void rmqinit ()
{
    for (int i=0 ; i<n ; ++i)
    dpmax[i][0]=dpmin[i][0]=w[i];
    int m=getln2(n);
    for (int i=1 ; i<=m ; ++i)
    {
        for (int j=n-1 ; j>=0 ; --j)
        {
            dpmax[j][i]=dpmax[j][i-1];//F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。
            if(j+(1<<(i-1))<=n)dpmax[j][i]=max(dpmax[j][i],dpmax[j+(1<<(i-1))][i-1]);
            dpmin[j][i]=dpmin[j][i-1];
            if(j+(1<<(i-1))<=n)dpmin[j][i]=min(dpmin[j][i],dpmin[j+(1<<(i-1))][i-1]);
        }
    }
}

int rmqmin(int l,int r)
{
    int m=getln2(r-l+1);//k:=trunc(ln(r-l+1)/ln(2));
    return min(dpmin[l][m],dpmin[r-(1<<m)+1][m]);//ans:=min(F[l,k],F[r-2^k+1,k]);
}

int rmqmax(int l,int r)//O(1)
{
    int m=getln2(r-l+1);
    return max(dpmax[l][m],dpmax[r-(1<<m)+1][m]);
}
int k;
int main ()
{
    while (~scanf("%d%d",&n,&k),(n||k))
    {
        
        //printf("%d\n",getln2(6));
        for (int i=0 ; i<n ; ++i)
        {
            scanf("%d",w+i);
        }
        rmqinit();
        int start=0,end=0;
        int ans=0;
        while(true)
        {
            int tmpmax=rmqmax(start,end);
            int tmpmin=rmqmin(start,end);
            //printf("%d  %d  start=%d e=%d\n",tmpmin,tmpmax,start,end);
            while(tmpmax-tmpmin<=k && end<n)
            {
                end++;
                tmpmax=rmqmax(start,end);
                tmpmin=rmqmin(start,end);
            }
            //printf("%d  %d  start=%d e=%d\n",tmpmin,tmpmax,start,end);
            ans=max(ans,end-start);
            tmpmax=rmqmax(start,end);
            tmpmin=rmqmin(start,end);
            //printf("%d  %d  start=%d e=%d\n",tmpmin,tmpmax,start,end);
            while(tmpmax-tmpmin>k && start <=end)
            {
                start++;
                tmpmax=rmqmax(start,end);
                tmpmin=rmqmin(start,end);
            }
            //printf("%d\n",ans);
            if(start+ans>n || end>=n )break;
        }
        printf("%d\n",ans);
    }
    return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值