RMQ
解釋:
RMQ 問題是求給定區間中的最值問題,如下圖所示:
RMQ 問題(圖中記錄的是最小值的位置)
當然,最簡單的算法是 O(n)的,但是對於查詢次數很多 m(假設有 100 萬次),則這個算法的時
間複雜度爲 O(mn),顯然時間效率太低。可以用線段樹將查詢算法優化到 O(logn)(在線段樹中保
存線段的最值) ,而線段樹的預處理時間複雜度爲 O(n),線段樹整體複雜度爲
ST表:
解釋:
ST 算法,即 Sparse Table 算法。下面把 ST 算法分成預處理和查詢兩部分來說明(以求最小值
爲例),它的時間複雜度爲
1.1.1 預處理:lll
預處理使用 DP 的思想,f(i, j)表示[i, i+2^j 1]區間中的最小值,即 f[i,j]表示從第 i 個數起連
RMQ 和 LCA
2
續 2^j 個數中的最小值。我們可以開闢一個數組專門來保存 f(i, j)的值。
例如,f(1, 0)表示[1,1]之間的最小值,就是 num[1];f(1, 2)表示[1, 4]之間的最小值, f(2, 4)
表示[2, 17]之間的最小值。
注意, 因爲 f(i, j)可以由 f(i, j 1)和 f(i+2^(j1), j1)導出, 而遞推的初值(所有的 f(i, 0)
=num[i])都是已知的。所以我們可以採用自底向上的算法遞推地給出所有符合條件的 f(i, j)的值。
ST 算法(圖中記錄的是最小值的位置)
ST 算法的狀態轉移方程:
1
[ ] , 0
( , )
min ( ( , 1), ( 2 , 1), 0
j
a i j f i j f i j f i j j -
Ï =
= Ì
Ó - + - >
例 如 : f(2,3) 保 存 的 是 a[2],a[3],a[4],……,a[9] 中 的 最 小 值 , 而
f(2,3)=min(f(2,2),f(6,2))=min((a[2],a[3],a[4],a[5]),(a[6],a[7],a[8],a[9]))
1.1.2 查詢:
假設要查詢從 m 到 n 這一段的最小值, 那麼我們先求出一個最大的 k, 使得 k 滿足
2 ( 1)
k £ n - m + ,於是我們就可以把[m, n]分成兩個(部分重疊的)長度爲 2
k
的區間:
[ , 2 1]
k
m m + - , [ 2 1, ]
k
n - + n ;
而 我 們 之 前 已 經 求 出 了 f (m, k ) 爲 [ , 2 1]
k m m + - 的 最 小 值 , ( 2 1, )
k f n - + k 爲
[ 2 1, ]
k
n - + n 的最小值。
我們只要返回其中更小的那個, 就是我們想要的答案, 這個算法的時間複雜度是 O(1)的。
RMQ 和 LCA
3
k= trunc(ln(rl+1)/ln(2)); // 求[l,r]之間的最小值
ans:=max(F[l,k],F[r2^k+1,k]);
例如, rmq(1,12) = min(f(1,3), f(5,3)) ( 2 k = log (12 -1+1) = 3 )
ST 算法的 O(1)查詢(有部分重疊)
function max(x,y: longint): longint;
begin
max:=x;
if y>x then max:=y;
end;
function query(s,t: longint): longint; // 查詢[s,t]間的最大值
var
k: longint;
begin
k:=trunc(ln(ts+1)/ln(2));
query:=max(a[s,k],a[t(1<<k)+1,t]); // 即max(a[s,k],a[t2^k+1,t])
end;
procedure init;
var
i,j,p: longint;
begin
assign(input,'rmqst.in');
reset(input);
readln(n);
for i:=1 to n do read(a[i,0]);
p:= trunc(ln(n)/ln(2));
for j:=1 to p do // a[i,j]表示從i開始,2^j個元素的最大值
for i:=1 to (n(1<<j)+1) do // n(1<<j)+1 即 n2^j+1
a[i,j]:=max(a[i,j1],a[i+1<<(j1),j1]);
close(input);
end;
1.2 巧妙易實現的分塊 sqrt(n)算法: (★★★)
把數組分割成 sqrt(n)大小的段。用一個數組 M[1..sqrt(n)]爲每一段保存最小值(或位置)。
M 可以很容易在 O(n)時間內完成預處理。
如在上例中,計算 RMQ(3,8)的值,我們應該比較 A[3]、M[2]、A[7]、A[8]值即可。可以很容
易看出這個算法每一次查詢不會超過 3*sqrt(n)次操作。
這個方法非常容易實現,並且還可以將它改成問題的動態版本 .... (邊查詢、邊改元素)。