RMQ-區間最值問題

區間最值詢問是求給定區間最值的問題。若總區間爲[1,N],通常是有多次查詢,每次查詢是不同的總區間的子區間。

簡單的方法是對每個子區間遍歷從而找到最值,時間複雜度是O(N),但是如果有多次的查詢,效率就會很低。而解決這個問題的一個很好的在線算法便是ST(Sparse_Table)算法

算法思想

預處理

ST算法在O(nlogn)的預處理以後可以實現O(1)的查詢效率。也就是說我們把大量的區間的最值預先求出來,之後查詢時就可以直接獲取。那麼重點首先就是預處理。

ST算法使用了動態規劃的思想:先計算一些區間的最小值,然後把每個詢問區間拆成若干個已經計算了最小值的區間,並統計這些區間的最小值的最小值,從而得到答案。
爲了更有效更快的預處理,我們把每個預處理的區間長度定爲2的非負整數次冪的,因此設定d[i][j]表示區間[i,i+2^j-1] 最小值(本文假設都是求最小值)。那麼我們就能得到動規方程:

        d[i][j] = min(d[i][j-1],d[i+2^j][j-1])(j>=1)

d[i][j-1] 表示 d[i][j] 前一半區間的最小值,d[i+2^j][j-1] 表示後一半區間的最小值。比如要求區間[1,8]的最值,那可以先求[1,4]與[5,8]的最值,[1,4]再分解爲[1,2],[3,4],[5,8]再分解爲[5,6],[7,8]。轉換爲動規過程即是:

  • d[1,3]= min(d[1,2],d[5,2])
    • d[1,2] =min(d[1][1],d[3][1])
    • d[5,2] = min(d(5,1),d[7,1])
      • d[1][1] = min(d[1][0],d[2][0])
      • d[3][1] = min(d[3][0],d[4][0])
      • d[5][1] = min(d[5][0],d[6][0])
      • d[7][1] = min(d[7][0],d[8][0])

分解下來是這樣子的,但是預處理的計算過程是從下向上一步步執行的。

j的遍歷範圍是[1,log(N)],而i的遍歷範圍[0,N+1-2^j]。所以得到預處理的時間複雜度是O(nlgn).

而區間的初始值(f[i][0] =i)都是已知的,整體的ST算法預處理步驟代碼如下:

for (int i = 0; i < N; ++i) {
        f[i][0] = a[i];
}

for (int j = 1; (1 << j) <= N; ++j) {
        for (int i = 0; i + (1 << j) - 1 < N; ++i) {
            f[i][j] = min(f[i][j-1], f[i+(1 << (j-1))][j-1]);
        }
}

求區間最值

預處理之後,每次查詢都快速求解出答案。
若查詢[a,b]之間的最小值,我們可以通過求 min(f[a,k],f[b-2^k+1,k]) 得出,其中K是滿足 2^k<=b-a+1的最大值, 這樣能夠滿足f[a,k]和f[b-2^k+1,k] 對應的區間覆蓋整個[a,b]. 因此
k= trunc(log_2(b-a+1)) = trunc(ln(b-a+1)/ln(2)). 求值代碼較簡單,此處就不貼了。

總結

ST巧妙的利用了二進制以及動態規劃思想,區間最值的種種特性,降低了代碼複雜度,其很好應用例子是求兩個節點的最小公共父節點。

發佈了42 篇原創文章 · 獲贊 32 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章