1. 程式人生 > >資料結構之求解RMQ問題

資料結構之求解RMQ問題

RMQ,即range minimum queuy,範圍最小值查詢,一般樸素演算法查詢單個區間是O(n),查詢m個就是O(m*n) ,這裡要說的Sparse-Table演算法,需要O(nlog n)的預處理,O(1)的單次查詢,在查詢次數很多的時候就能體現更好的優越性。而且,最重要的是這個演算法寫法簡單,理解方便~~

令d(i,j)表示從i開頭的,長度為2^j(本文中^都表示次方而不是異或)的區間內的最小值,則可以使用遞推來求d(i,j):

d(i,j)=min(d(i,j-1), d(i+2^{j-1}, j-1))

這不難理解啊,直接將2^j個元素分兩半了,然後取左邊2^(j-1)個元素的最小值和2^(j-1)個元素的最小值的最小值~顯然這個遞推是O(1)的,然後i有n個,由於2^j <= n,也就是 j <= log n,所以d陣列有不超過nlog n個元素,所以預處理複雜度為O(nlog n)

然後查詢,如何從d裡面O(1)地查到任意區間[L, R]的最小值呢?

實際上,我們也可以像上面遞推一樣,分成兩個部分,但是由於區間長度n = R-L+1不一定是2的冪,所以可能不能恰好分成兩部分——但是,值得慶幸的是這是求最小值,並不需要保證兩個區間不能有交,只需要保證兩個區間並起來是[L,R]即可~也就是說只要把[L,R]分成倆區間[L, L+2^k-1]和[R-2^k+1, R]即可~然後怎麼求這個k呢?顯然是滿足2^k <= n的最大的k就好了啊,也可以預處理出來~

程式碼:

#include<vector>
#include<cmath>
#include<algorithm>
#define rgt register int

using namespace std;
const int maxn = 10010;             // 最大元素個數
int d[maxn][(int)log(maxn)+1];      // d陣列元素個數不超過nlog n,而每一個都可以在常數時間計算完畢
int K[maxn];                        // K[i]為滿足(1<<k) <= i的最大整數,K[0]無意義
void initRMQ(const vector<int>& A)  // 對A進行預處理
{
    int n = A.size();
    for(rgt i = 0; i < n; ++ i)
        d[i][0] = A[i];
    for(rgt j = 1; (1<<j) <= n; ++ j) //O(nlog n)處理d陣列
        for(rgt i = 0; i+(1<<j)-1 < n; ++ i)
            d[i][j] = min(d[i][j-1], d[i+(1<<(j-1))][j-1]);

    int p = 1;
    K[0] = -1;
    for(int i = 1; i <= n; ++ i)
    {
        if(i == p)
        {
            K[i] = K[i-1] + 1;
            p <<= 1;
        }
        else
            K[i] = K[i-1];
    }
}

int RMQ(int L, int R)
{
    int k = K[R-L+1];
    return min(d[L][k], d[R-(1<<k)+1][k]);
    //從L開始、以R結尾的1<<k的區間合起來覆蓋了[L,R],由於是取最小值,有元素重複沒關係~
}