RMQ算法

RMQ算法(Range Minimum/Maximum Query),即區間最值查詢,是一種在線算法。

補充:所謂在線算法就是每次輸入可立刻處理一個結果,對比離線算法,離線算法是要知道所有的輸入之後纔開始處理結果,例如選擇排序和插入排序,選擇排序是一種離線算法,排序之前要知道所有的待排序元素,而插入排序就不必知道,所以插入排序是一種在線算法。

RMQ算法一般用比較長的時間預處理,預處理時間複雜度是log級,然後查詢的時間只有O(1)。

下面我舉一個例子來講一下RMQ的求最小值預處理部分。

假設有一個數組a:1 5 3 1 2 5 2 4 8 9

設二維數組dp[i][j]表示從第i位開始連續 2^j 個數中的最小值。則dp[1][1]就是表示從第一位開始後面連續兩位的最小值,即第一位和第二位的最小值。dp[2][1]表示第2位開始後面連續兩位的最小值,即第二位和第三位的最小值。

而dp[i][j]是表示從第i位開始連續 2^j 個數中的最小值, 其實表示的是一段區間的最小值, 我們把這個區間劃分開來成兩個區間

即 dp [i][j - 1] 和 dp [i + (1 << j - 1)][j - 1]

j是指數值, 所以指數減一相當於真數除以了底數倍, 底數爲2, 相當於將一段從中間分開,此時只要求這兩段的最小值就可以了, 所以我們很容易寫出轉移方程

dp[i][j] = min(dp [i][j - 1], dp [i + (1 << j - 1)][j - 1])

代碼如下:

//kuangbin templet(查詢最大值) 一維
//若想查最小,看提示更改
const int MAXN= 50000 + 9 ;
int dp[MAXN][20];//第二維是範圍,即2^20約等於100萬
//PS 如果同時要求最大最小,要多開一個dp2[][]來存最小
int mm[MAXN];//mm是間接存的數組


//b[]纔是數據,並且b從1開始
void initRMQ(int n,int b[])
{
    mm[0]=-1;
    for (int i=1;i<=n;i++)
    {
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
        dp[i][0]=b[i];
    }
    for (int j=1;j<=mm[n];j++)
        for (int i=1;i+(1<<j)-1<=n;i++)
            dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
            //PS 若最小 max 改爲 min
}

說幾個我當時沒看太懂的點, mm數組求的是原數組長度n以2爲底的對數值, 因爲2^j<n所以在循環範圍時j小於n以2爲底的對數值即可, 這樣不會過多的求出不必要的數據

爲什麼外層循環是j, 爲什麼不能先循環i ?

這個狀態方程的含義是:先更新每兩個元素中的最小值,然後通過每兩個元素的最小值獲得每4個元素中的最小值,依次類推更新所有長度的最小值。所以我們要先求j 纔可以

查詢部分我以爲就是理所應當的查詢就好了, 但是看到了一位大佬的博客,對於爲什麼在dp數組中的查詢結果是正確的給出了嚴謹的數學證明, 我覺得很有意義, 再此引用

 

但是爲什麼這樣就可以保證是區間最小值了呢?

 

dp[l][k]維護的是區間 [l, l + 2^k - 1] , dp[r - (1 << k) + 1][k]維護的是區間 [r - 2^k + 1, r] 。

那麼只要我們保證  ≤ 就能保證RMQ[l,r] = min(dp[l][k], dp[r - (1 << k) + 1][k]);


 

接下來我們用分析法來證明這個不等式:

我們假設   ≤  這個等式成立

即有 r - l + 2 ≤  也就是 r - l + 2 ≤ 

又因爲 k = ;

那麼 r - l + 2 ≤ 2 * (r - l +1)

則 r - l + 2 ≤ 2*(r - l) + 2

即 r - l ≤ 2*(r-l)

所以 r - l ≥ 0,即假設成立

我們舉個例子, l = 4,r = 6;

假設數組arr爲:1,2,6,8,4,3,7

此時 k =  =  = 1

則區間[4,6]的最小值 = min(dp[4][1],dp[5][1])

dp[4][1] = 4,dp[5][1] = 3,所以區間[4,6]的最小值 = min(dp[4][1],dp[5][1]) = 3

我們很容易看出來答案是正確的。


 ———————————————— 
原文鏈接:https://blog.csdn.net/qq_41311604/article/details/79900893

查詢部分代碼

int RMQ(int x,int y)
{
    int k=mm[y-x+1];
    return max(dp[x][k],dp[y-(1<<k)+1][k]);
    //PS 若最小 max 改爲 min
}

完整代碼

//kuangbin templet(查詢最大值) 一維
//若想查最小,看提示更改
const int MAXN= 50000 + 9 ;
int dp[MAXN][20];//第二維是範圍,即2^20約等於100萬
//PS 如果同時要求最大最小,要多開一個dp2[][]來存最小
int mm[MAXN];//mm是間接存的數組


//b[]纔是數據,並且b從1開始
void initRMQ(int n,int b[])
{
    mm[0]=-1;
    for (int i=1;i<=n;i++)
    {
        mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
        dp[i][0]=b[i];
    }
    for (int j=1;j<=mm[n];j++)
        for (int i=1;i+(1<<j)-1<=n;i++)
            dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
            //PS 若最小 max 改爲 min
}
int RMQ(int x,int y)
{
    int k=mm[y-x+1];
    return max(dp[x][k],dp[y-(1<<k)+1][k]);
    //PS 若最小 max 改爲 min
}

 

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