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