資料結構之求解RMQ問題
阿新 • • 發佈:2018-10-31
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):
這不難理解啊,直接將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],由於是取最小值,有元素重複沒關係~ }