1. 程式人生 > >RMQ倍增求求區間最值

RMQ倍增求求區間最值

RMQ演算法全稱為(Range Minimum/Maximum Query)意思是給你一個長度為n的陣列A,求出給定區間的最值的下標。當然我們可以採用列舉,但是我們也可以使用線段樹來優化,複雜度為(nlogn),但是最好的辦法是採用Sparse_Table演算法,簡稱ST演算法。他能在進行(nlogn)的預處理後達到n(1)的效率。下面來分析下最大值和最小值,都要用到DP的思想。

最小值(Mininun):我們可以用F(i,j)表示區間[i,i+2^j-1]間的最小值。我們可以開闢陣列來儲存F(i,j)的值,例如:F(2,4)就是儲存區間[2,2+2^4-1]=[2,17]的最小值。那麼F(i,0)的值是確定的,就為i這個位置所指的元素值,這時我們可以把區間[i,i+2^j-1]平均分為兩個區間,因為j>=1的時候該區間的長度始終為偶數,可以分為區間[i,i+2^(j-1)-1]和區間[i+2^(j-1)-1,i+2^j-1],即取兩個長度為2^(j-1)的塊取代和更新長度為2^j的塊,那麼最小值就是這兩個區間的最小值的最小值,動態規劃為:F[i,j]=min(F[i,j-1],F[i+2^(j-1)-1,j-1]).同理:最大值就是F[i,j]=max(F[i,j-1],F[i+2^(j-1)-1,j-1]).

因為我們把區間分成相同的兩部分有i+2^(j-1)-1這個點重複了,我們也可以這樣寫F[i,j]=min(F[i,j-1],F[i+2^(j-1),j-1]).同理:最大值就是F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1]).

現在求出了F[i,j]之後又是怎樣求出最大值或者最小值的,怎麼轉換為o(1)這種演算法的~這就是ST演算法:

這個時候詢問時只要取k=ln(j-i+1)/ln2即可,那麼可以令A為i到i+2^k-1長度為2^k的塊,和B為j-2^k+1到j-2^k+1+2^k-1的長度為2^k的塊;那麼A,B都是區間[i,j]的子區間,所以即求A區間的最小值和B區間的最小值的最小值。這個時候動態規劃為:

RMQ(i,j)=min(F[i,k],F[j-2^k+1,k]);

程式碼模板:

void RMQ()  
{  
    int i,k;
    for(i=1;i<=n;i++)
    f[i][0]=a[i];
    for(k=1;k<=20;k++)  
    for(i=1;i<=n;i++)  
    if(i + (1 << k)-1<=n)  
    f[i][k]=GCD(f[i][k-1],f[i+(1<<(k-1))][k-1]);  
}  
int query(int l,int r)  
{  
    int k=(int)(log(r-l+1.0)/log(2.0));  
   return GCD(f[l][k], f[r - (1 << k) +1][k]);  
}