1. 程式人生 > >提高篇(1):RMQ問題與ST表

提高篇(1):RMQ問題與ST表

style 總結 線段 區間 選擇 線段樹 支持 ins 例題

RMQ是英文Range Minimum/Maximum Query的縮寫,是詢問某個區間內的最值,這裏講一種解法:ST算法

ST算法通常用在要多次(10^6級別)詢問區間最值的問題中,相比於線段樹,它實現更簡單,效率更高,但不支持修改,且一般只能維護最值。

ST算法實際上是動規,原理如下:

預處理:

一組數a[1]..a[n],設f[i][j]表示從a[i]到a[i+2^j-1]這個範圍中的最值,元素個數為2^j個。

可以分成2部分,即從a[i]至a[i+2^(j-1)-1]與a[i+2^(j-1)]至a[i+2^j-1],所以

f[i][j]也可以分成f[i][j-1]與f[i+2^(j-1)][j-1],整個區間的最大值一定是左右兩部分最大值的較大值,

於是可得狀態轉移方程:f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1]),邊界條件為f[i][0]=a[i],這樣即可在O(n log(n))的時間內預處理f數組。

詢問:

若詢問區間[l,r]的最大值,可以先求出最大的x,滿足2^x<=r-l+1,那麽區間[l,r]=[l,l+2^x-1]U[r-2^x+1,r],兩個區間的元素個數都為2^x,

所以[l,r]中的最大值為max(f[l][x],f[r-2^x+1][x]),可以在O(1)內計算出來(對於m次詢問,需要O(m)的時間復雜度)。這兩個區間雖然有交集,但對最值沒有影響,這就是ST算法只使用於區間最值的原因。


總結:

求區間[x,y]的最大值:

k=log2(y-x+1);

ans=max(f[x][k],f[y-2^k+1][k]);

技巧:

因為cmath庫中的log2函數效率不高,所以為了提高速度,通常會使用O(N)的遞推預處理出1~N這N種區間長度各自對應的k值。

具體地,設log[x]表示log2(x)向下取整,則log[x]=log[x/2]+1。這樣總時間復雜度為log(n*log(n)+m+n)。

放一道例題:

平衡陣容(Balanced Lineup)

題目描述

天,農夫 John 的N(1 <= N <= 50,000)頭牛總是按同一序列排隊. 有一天, John決定讓一些牛們玩一場飛盤比賽. 他準備找一群在對列中為置連續的牛來進行比賽. 但是為了避免水平懸殊,牛的身高不應該相差太大. John 準備了Q (1 <= Q <= 180,000) 個可能的牛的選擇和所有牛的身高 (1 <= 身高 <= 1,000,000). 他想知道每一組裏面最高和最低的牛的身高差別. 註意: 在最大數據上, 輸入和輸出將占用大部分運行時間.

輸入

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

輸出

6

3

0

[參考程序]

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<climits>
 5 #include<cmath>
 6 #include<algorithm>
 7 using namespace std;
 8     
 9 const int N = 50005;
10 int FMAX[N][20], FMIN[N][20];
11     
12 void RMQ(int n)
13 {
14     for(int j = 1; j != 20; ++j)
15     {
16         for(int i = 1; i <= n; ++i)
17         {
18             if(i + (1 << j) - 1 <= n)
19             {
20                 FMAX[i][j] = max(FMAX[i][j - 1], FMAX[i + (1 << (j - 1))][j - 1]);
21                 FMIN[i][j] = min(FMIN[i][j - 1], FMIN[i + (1 << (j - 1))][j - 1]);
22             }
23         }
24     }
25 }
26     
27 int main()
28 {
29     int num, query;
30     int a, b;
31     while(scanf("%d %d", &num, &query) != EOF)
32     {
33         for(int i = 1; i <= num; ++i)
34         {
35             scanf("%d", &FMAX[i][0]);
36             FMIN[i][0] = FMAX[i][0];
37         }
38         RMQ(num);
39         while(query--)
40         {
41             scanf("%d%d", &a, &b);
42             int k = (int)(log(b - a + 1.0) / log(2.0));
43             int maxsum = max(FMAX[a][k], FMAX[b - (1 << k) + 1][k]);
44             int minsum = min(FMIN[a][k], FMIN[b - (1 << k) + 1][k]);
45             printf("%d\n", maxsum - minsum);
46         }
47     }
48     return 0;
49 }

參考書籍:《信息學奧賽一本通·提高篇》

提高篇(1):RMQ問題與ST表