1. 程式人生 > >『資料結構』RMQ問題

『資料結構』RMQ問題

RMQ(Range Minimum/Maximum Query),即區間最值問題。

對於長度為 n 的數列 A ,回答若干查詢 RMQ(A,i,j)(i,j<=n) ,返回數列 A 中下標在 i,j 裡的最大(小)值。

相關演算法

  1. 樸素(搜尋),時間複雜度:\(O(n)-O(q \times n)\) ,線上;
  2. 線段樹,時間複雜度:$O(n)-O(q\times logn) $,線上;
  3. ST(動態規劃),時間複雜度:\(O(n\times logn)-O(q)\),線上;
  4. RMQ標準演算法,先規約為LCA,再規約成約束RMQ ,時間複雜度:\(O(n)-O(q)\)
    ,線上。

ST 演算法

假設當前題目要求區間最小值,我們令 dp[i][j] 代表從 i 開始,長度為\(2^{j}\)這段區間的最小值。

於是便有:\(dp[i][j]=min(dp[i][j-1],dp[i+^{j-1}][j-1])\)

分析可知,\(dp[i][j-1]\)代表從 i 開始,長度為\(2^{j}\)區間一半中的最小值,而 \(dp[i+2^{j-1}][j-1]\)即為區間的另一半。

即為區間的另一半。

最終(從下往上看):

\(dp[0][*]\) \(dp[1][*]\) \(dp[2][*]\) \(dp[3][*]\)
\(dp[4][*]\) \(dp[5][*]\) \(dp[6][*]\) \(dp[7][*]\)
\(dp[*\)][3] \(1\)
\(dp[*\)][2] \(1\) \(1\) \(1\) \(5\) \(2\)
\(dp[*][1]\) \(3\) \(1\) \(1\) \(5\) \(7\)
\(6\) \(2\)
\(dp[*][0]\) \(4\) \(3\) \(1\) \(5\) \(7\) \(8\) \(6\) \(2\)

預處理

根據狀態轉移方程,首先指定當區間長度為\(2^{0}\)時的各初始值,隨後推出後面的結果。

void ST_Init(const vector<int> &A) {
    int n=A.size();
    for (int i=0; i<n; i++)
        dp[i][0]=A[i];
    for (int j=1; (1<<j)<=n; j++)
        for (int i=0; i+(1<<j)<=n; i++)
            dp[i][j]=min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}

查詢

預處理出整個 dp 陣列以後,查詢操作很簡單,令 k 為滿足\(2^{k} \leq R-L+1\)的最大整數,則以 L 開頭、以 R 結尾的兩個長度為\(2^{k}\)的區間合起來即覆蓋了查詢區間 [L,R]

int RMQ(int L, int R) {
    int k=0;
    while ((1<<(k+1))<=R-L+1) k++;
    return min(dp[L][k], dp[R-(1<<k)+1][k]);
}

嗯!怎麼說呢?感覺線段樹在這種型別的題目中好像是最萬能的方法了。

無論是 [點修改+查詢] 還是 [區間修改+查詢] ,它都可以做到 \(O(logn)\)的複雜度,而且線上段樹中我們也可以維護好多東西(區間和、最值等等)。

對於一維中的線段樹,我們想要查詢某個區間的最值,首先就應該建樹咯~(具體方法省略

而在查詢時,我們可以從根節點向下遞迴搜尋,如下圖,假設查詢區間為 [2,6]

[2,6] 這一個大區間分解為不相交的三個小區間 [2,3]、[4,5]、[6] ,而最終的結果便由這三個節點中所維護的資訊決定的!

1.png

我們假設查詢還是區間最小值,於是最終的結果為\(\min(1,7,6)=1\)

線段樹可以解決普通的 [點/區間] 修改+查詢 ,當然它也可以解決 樹中的路徑權值 修改+查詢(樹鏈剖分)。