RMQ(ST算法)


RMQ(Range Minimum/Maximum Query),即區間最值查詢,是指這樣一個問題:對於長度為n的數列a,回答若干詢問RMQ(A,i,j)(i, j<=n),返回數列a中下標在i,j之間的最小/大值。如果只有一次詢問,那樣只有一遍for就可以搞定,但是如果有許多次詢問就無法在很快的時間處理出來。在這里介紹一個在線算法。所謂在線算法,是指用戶每輸入一個查詢便馬上處理一個查詢。該算法一般用較長的時間做預處理,待信息充足以后便可以用較少的時間回答每個查詢。ST(Sparse Table)算法是一個非常有名的在線處理RMQ問題的算法,它可以在O(nlogn)時間內進行預處理,然后在O(1)時間內回答每個查詢。

步驟如下:

假設a數組為:

1, 3, 6, 7, 4, 2, 5

1.首先做預處理(以處理區間最小值為例)

設mn[i][j]表示從第i位開始連續2^j個數中的最小值。例如mn[2][1]為第2位數開始連續2個的數的最小值,即3, 6之間的最小值,即mn[2][1] = 3;

之后我們很容想到遞推方程:

mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1])

附上偽代碼:

for(int j = 0; j < 20; j ++)
    for(int i = 1; i + (1 << j) <= n + 1; i ++)
        mn[i][j] = min(mn[i][j - 1], mn[i + (1 << (j - 1))][j - 1]);

咦?為什么第二行是i + (1 << j) <= n + 1呢?因為mn[i][j]表示連續2^j個數,所以mn[i][j]所維護的區間為[i, i + (1 << j) - 1],所以在最后要+1,其實是為了方便,寫成i + (1 << j) - 1 <= n感覺左邊太長了,所以寫在右邊了。

那么為什么j要寫在外圍?如果寫在里面的輸出結果是這樣的

我們會發現沒有更新過,這是為什么呢? 因為我們在更新的時候是通過要通過2^(j - 1)的區間來更新2^j的區間,來看狀態轉移方程:

mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1])

我們發現如果j寫在里面的話,在更新mn[i][j]的時候會發現mn[i +(1<<j - 1)][j - 1]還沒有更新,所以才會出現這樣的結果,正確結果如下:

 

咦?為什么還有0?我們來看偽代碼:

for(int j = 0; j < 20; j ++)
    for(int i = 1; i + (1 << j) <= n + 1; i ++)
        mn[i][j] = min(mn[i][j - 1], mn[i + (1 << (j - 1))][j - 1]);

看第二行會發現,對於i + (1  << j) - 1超過n的,我們沒有更新,如圖中的mn[5][2],5 + 2^2 - 1 = 8 > 7所以沒有更新,但這並不影響詢問的結果。

2.查詢

假設我們需要查詢區間[l, r]中的最小值,令k = log2(r - l + 1); 則區間[l, r]的最小值RMQ[l,r] = min(mn[l][k], mn[r - (1 << k) + 1][k]);

那么為什么這樣就可以保證為區間最值嗎?

mn[l][k]維護的是[l, l + 2 ^ k - 1], mn[r - (1 << k) + 1][k]維護的是[r - 2 ^ k + 1, r] 。

那么我們只要保證r - 2 ^ k + 1 <= l + 2 ^ k - 1就能保證RMQ[l,r] = min(mn[l][k], mn[r - (1 << k) + 1][k]);

我們用分析法來證明下:

若r - 2 ^ k + 1 <= l + 2 ^ k - 1;

則r - l + 2 <= 2 ^ (k + 1);

又因為 k = log2(r - l + 1);

則r - l + 2 <= 2 *(r - l + 1);

則r - l >= 0;

顯然可得。

由此得證。

我們來舉個例子 l = 4, r = 6;

此時k = log2(r - l + 1) = log2(3) = 1;

所以RMQ[4, 6] = min(mn[4][1], mn[5][1]);

mn[4][1] = 4, mn[5][1] = 2;

所以RMQ[4, 6] = min(mn[4][1], mn[5][1]) = 2;

我們很容易看出來了答案是正確的。

附上總代碼:(以結構體的形式寫出):

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 const int N = 100000 + 5;
 5 
 6 int a[N];
 7 
 8 int mn[N][25];
 9 
10 int n, q, l, r;
11 
12 struct RMQ{
13     int log2[N];
14     void init(){
15         for(int i = 0; i <= n; i ++)log2[i] = (i == 0 ? -1 : log2[i >> 1] + 1);
16         for(int j = 1; j < 20; j ++)
17             for(int i = 1; i + (1 << j) <= n + 1; i ++)
18                 mn[i][j] = min(mn[i][j - 1], mn[i + (1 << j - 1)][j - 1]);
19     }
20     int query(int ql, int qr){
21         int k = log2[qr - ql + 1];
22         return min(mn[ql][k], mn[qr - (1 << k) + 1][k]);
23     }
24 }rmq;
25 
26 void work(){
27     rmq.init();
28     scanf("%d", &q);
29     while(q --){
30         scanf("%d%d", &l, &r);
31         printf("%d\n", rmq.query(l, r));
32     }
33 }
34 
35 int main(){
36     while(scanf("%d", &n) == 1){
37         for(int i = 1; i <= n; i ++)scanf("%d", a + i), mn[i][0] = a[i];
38         work();
39     }
40     return 0;
41 }
View Code

 

 參考論文:http://blog.csdn.net/niushuai666/article/details/6624672/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM