提高篇(1):RMQ問題與ST表
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表