因為之前都是線段樹解決rmq問題,省腦子……(因為寫的順手)……
但是因為rmq畢竟常數大,而且其實程式還相對st要長…… 以前寫過st演算法,但是因為每次都在糾結到底+1 呢,還是-1呢, 邊界問題處理的我蛋都碎了好幾次,這次重新學習了一下st(sparse table)演算法,好好的處理了這些問題的理解。
st演算法: 解決rmq問題。 rmq問題:
區間最大值,最小值問題……
時間複雜度: 預處理nlogn, 查詢o(1)。 預處理和查詢的常數都極小。線段樹雖然是nlogn預處理,但是常數略大,而且查詢是logn的…… 當然了,線段樹還有其他支援的操作,暫且不談。
st演算法侷限性:解決靜態rmq問題。 但是! rmq和字尾陣列聯手,就非常好用了!
正題:(這個字有點太小了?趕緊換大號字)
這麼大的字看起來舒服多了,對於我這近視眼而言。。
這樣的字靠譜點……就這樣好了。
st[i][j] 表示,, r[i] r[i + 1] r[i + 2] .... r[i + 2^j - 1] 這一連串,陣列下標從 i到i + 2^j -1的區間內,最值是多少。(從這裡開始,最值就當最小值了,最大值同理。)
那麼顯然,st[i][0] = r[i]; 因為 r[i] .. r[i + 1] ... r[i + 2^0 - 1] 實質上就是 r[i]到r[i]這個區間……也就是說,初始化st[i][0] = r[i]即可。
下面重點來了, st[i][j] = min( s[i][j - 1] , s[i + 2^(j-1)][j-1]是怎麼來的呢?
我們舉個例子。
當i = 3, j = 3的時候, st[3][3]所表示的區間就是 3 4 5 6 [7 8 9 10]
i = 3, j = 2的時候, st[3][2] 所表示的區間顯然是 3 4 5 6
這個時候我們就可以明顯看出來了,我們需要從7的位置開始 也就是需要st[7][2] 所表示的區間 7 8 9 10
那麼st[3][3] = min(st[3][2] , st[7][2]);
這樣的話,構造過程就出來了。
下面的程式就表示,如何構造出最大和最小的兩個情況。
void makermq(int *r, int n)
}
i =1開始窮舉2^i ,
st[0][i] 為 [0] [1] [2] [3] ... [2^i -1] 的區間。 如果出現了[2^i -1]所表示的下標,已經達到,甚至超過n的話,顯然就不需要再計算了,
2^i - 1 也就是 (1 << i ) - 1, 他的值必須要比n要小(0為下標起點)。
第二層迴圈
j 作為窮舉的區間起點位置(終點位置是j + 2^i -1), 那麼他的終點位置也必須在n以為。 所以就是
j + (1 << i) - 1 < n
至於判斷取值的情況……
[j, j + 2^i - 1]整個區間,拆成2個一樣長的區間。
[ j, j + 2^(i - 1) - 1, ] 區間, 和[ j + 2^(i - 1), j + 2^i - 1] 兩個區間,後者實質就是st[j + 2^(i - 1)][i - 1];
整個區間最小值,也就是
stmin[j][i] = min(stmin[j][i - 1], stmin[j + (1 << (i - 1))][i - 1]);
這樣就解決了初始化的問題。 時間複雜度nlogn
***************
接下來是如何實現o(1)的查詢。
假如要查詢 3 4 5 6 7 8這個區間的最小值,實際上,我們給他分成兩份
查詢 3 4 5 6 和5 6 7 8 的最小值,兩者取最小就行了。 因為只要有交集,並且並集能覆蓋整個區間,那麼他的最小值一定就是他們整個區間的最小值。
下面就是思考如何知道就是從st[3][2]和st[5][2]這2個地方查詢呢?
不管怎麼樣,l + 2^i - 1 (這也就是st[l][i]所表示區間的最右邊) 必須要比r小或者相等…… 區間:l, l + 2 ^i - 1
r - 2^i + 1又一定要比l要大或者相等 區間 : r - 2^i + 1, r
同時,他們還要有並集!也就是r - 2 ^i + 1 >= l + 2 ^i - 1
綜上:要找出乙個最小的i,滿足
l + 2 ^i - 1 >= r - 2 ^i + 1
化簡後i = log(r - l + 2) / log(2) - 1 的結果向上取整(如果本身是整數,則不動)
這個式子用c語言表示很複雜- -我直接省事用dd
i = int(log(r - l + 2)/log(2) + 0.99999) - 1; 表示, 是可以通過測試的……
當然,這個表示方法並不科學,我們沒有利用好很多條件。
還看這個式子:
r - 2 ^i + 1 >= l + 2 ^i - 1
還可以寫成 l + 2^i - 1 > r - 2^i + 1
化簡得 i + 1 > (log(r-l+1) /log(2))
小於號右邊的式子,是乙個整數,
根據乙個結論: 乙個帶小數點的數字,取整+1,一定大於這個數字本身。
這麼看來,i作為右邊那個帶小數點的數字即可。 i = (int) (log(r-l+1) /log(2))
當然,i = (int)
(log(r-l+1) /log(2))的寫法,還可以通過其他方式解釋,比如區間長度,但是我感覺思維量略大,我不想多想了
這樣一來,程式就出來了。 flag表示詢問是大還是小
int ask(int l, int r, int flag)
全部程式如下:
#include #include #include #include using namespace std;
const int max_n = 50000 + 10;
int n, questions;
int stmax[max_n][20], stmin[max_n][20];
int a[max_n];
void makermq(int *r, int n)
}int ask(int l, int r, int flag)
int main()
return 0;
}
poj 3264 RMQ問題 ST演算法
部落格已遷至 www.lfy2us.com 比較裸的rmq問題,即對乙個無序陣列,查詢q次,每次查詢某個區間內最大值與最小值之差。解決此問題的演算法有三種 1 最簡單的方法就是o n 了 2 線段樹 3 st sparse table 演算法 其中st演算法為解此問題的最佳方法 預處理時間複雜度o ...
poj3264 RMQ問題的ST演算法
rmq range minimum maximum query 即查詢給定區間的最值。問題描述 對於給給定的陣列a 1 n 回答若干次查詢檔案rmq i,j 即返回陣列a i j 的最大值 最小值 問題分析 該問題可以使用樸素查詢法,也可以用線段樹,st演算法,rmq與lca互相轉換。這裡我們分析高...
poj3264 rmq演算法學習 ST表
解題關鍵 rmq模板題,可以用st表,亦可用線段樹等資料結構 log10和log2都可,這裡用到了對數的換底公式 類似於區間dp,用到了倍增的思想 f i j min f i j 1 f i 1 j 1 j 1 1 include2 include3 include4 include5 includ...