POJ 3264 RMQ問題 ST演算法

2021-06-28 16:07:55 字數 3263 閱讀 4642

因為之前都是線段樹解決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...