RMQ (Range Minimum/Maximum Query),即區間最值問題。
對於長度為 n 的數列 A ,回答若干查詢 RMQ(A,i,j)(i,j<=n) ,返回數列 A 中下標在 i,j 裡的最大(小)值。
相關演算法 樸素(搜尋),時間複雜度: O(n)−O(q×n) O(n)-O(q×n) ,online 線段樹,時間複雜度: O(n)−O(q×logn) O(n)-O(q×\log n) ,online ST(動態規劃),時間複雜度: O(n×logn)−O(q) O(n×\log n)-O(q) ,online RMQ標準演算法,先規約為 LCA ,再規約成約束 RMQ ,時間複雜度: O(n)−O(q) O(n)-O(q) ,online
ST 演算法
假設當前題目要求區間最小值,我們令 dp[i][j] 代表從 i 開始,長度為 2j 2^j 這段區間的最小值。
於是便有: dp[i][j]=min(dp[i][j−1],dp[i+2j−1][j−1]) dp[i][j]=\min(dp[i][j-1],dp[i+2^{j-1}][j-1])
分析可知, dp[i][j−1] dp[i][j-1] 代表從 i 開始,長度為 2j 2^j 區間一半中的最小值,而 dp[i+2j−1][j−1] 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 |
寫一組資料,自己動手類比一遍就可以理解咯~
預先處理
根據狀態轉移方程,首先指定當區間長度為 20 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 為滿足 2k<=R−L+1 2^k 的最大整數,則以 L 開頭、以 R 結尾的兩個長度為 2k 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) O(\log n) 的複雜度,而且線上段樹中我們也可以維護好多好多東西(區間和、最值等)。
當然,除了一維情況下的普通線段樹,還有在二維平面中的線段樹,類似的也可以實現三維、四維。。。(啊。千千你要做什麼o((>ω< ))o)
好啦~
對於一維中的線段樹,我們想要查詢某個區間的最值,首先就應該建樹咯~(具體方法省略)
而在查詢時,我們可以從根節點向下遞迴搜尋,如下圖,假設查詢區間為 [2,6] 。
將 [2,6] 這一個大區間分解為不相交的三個小區間 [2,3]、[4,5]、[6] ,而最終的結果便由這三個節點中所維護的資訊決定咯。
我們假設查詢還是區間最小值,於是最終的結果為