rmq的st算法及模板

 

        RMQ (Range Minimum/Maximum Query)問題是指:對於長度爲n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j裏的最小(大)值,也就是說,RMQ問題是指求區間最值的問題。

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

#include <iostream>   
#include <cstdio>   
#include <cmath>   
#define max(a,b) (a>b?a:b)   
#define min(a,b) (a<b?a:b)   
#define MN 50005   
using namespace std;   
int mi[MN][17],mx[MN][17],w[MN];   
int n,q;   
void rmqinit()   
{  
   int i,j,m;   
    for(i=1;i<=n;i++)
    {
        mi[i][0]=mx[i][0]=w[i];
    }   
    m=floor(log((double)n)/log(2.0));   
    for(i=1;i<=m;i++)   
    {
      for(j=n;j>=1;j--)   
        {
          mx[j][i]=mx[j][i-1];   
            if(j+(1<<(i-1))<=n)
                 mx[j][i]=max(mx[j][i],mx[j+(1<<(i-1))][i-1]);
            mi[j][i]=mi[j][i-1];   
            if(j+(1<<(i-1)<=n))
                 mi[j][i]=min(mi[j][i],mi[j+(1<<(i-1))][i-1]);   
         }   
    }   
}   
int rmqmin(int l,int r)   
{
   int m=floor(log((double)(r-l+1))/log(2.0));   
     return min(mi[l][m],mi[r-(1<<m)+1][m]);   
}   
int rmqmax(int l,int r)   
{
   int m=floor(log((double)(r-l+1))/log(2.0));   
     return max(mx[l][m],mx[r-(1<<m)+1][m]);   
}   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章