RMQ(Range Minimum/Maximum Query)問題:
RMQ問題是求給定區間中的最值問題。當然,最簡單的演算法是O(n)的,但是對於查詢次數很多(設定多大100萬次),O(n)的演算法效率不夠。可以用線段樹將演算法最佳化到O(logn)(線上段樹中儲存線段的最值)。不過,Sparse_Table演算法才是最好的:它可以在O(nlogn)的預先處理以後實現O(1)的查詢效率。下面把Sparse Table演算法分成預先處理和查詢兩部分來說明(以求最小值為例)。
預先處理:
預先處理使用DP的思想,f(i, j)表示[i, i+2^j - 1]區間中的最小值,我們可以開闢一個數組專門來儲存f(i, j)的值。
例如,f(0, 0)表示[0,0]之間的最小值,就是num[0], f(0, 2)表示[0, 3]之間的最小值, f(2, 4)表示[2, 17]之間的最小值
注意, 因為f(i, j)可以由f(i, j - 1)和f(i+2^(j-1), j-1)匯出, 而遞推的初值(所有的f(i, 0) = i)都是已知的
所以我們可以採用自底向上的演算法遞推地給出所有合格f(i, j)的值。
查詢:
假設要查詢從m到n這一段的最小值, 那麼我們先求出一個最大的k, 使得k滿足2^k <(n - m + 1).
於是我們就可以把[m, n]分成兩個(部分重疊的)長度為2^k的區間: [m, m+2^k-1], [n-2^k+1, n];
而我們之前已經求出了f(m, k)為[m, m+2^k-1]的最小值, f(n-2^k+1, k)為[n-2^k+1, n]的最小值
我們只要返回其中更小的那個, 就是我們想要的答案, 這個演算法的時間複雜度是O(1)的.
例如, rmq(0, 11) = min(f(0, 3), f(4, 3))
由此我們要注意的是預先處理f(i,j)中的j值只需要計算log(n+1)/log(2)即可,而i值我們也只需要計算到n-2^k+1即可。
以上資訊轉自網上, 總結下上面所訴:
(1).dp的狀態轉移方程為:
if(j == 0) dp[i][j] = 0;
else dp[i][j] = max(dp[i][j-1], dp[i+(1<<(j-1))][j-1]) //求最大值,最小值也一樣
(2).給你一個區間[L, R], 怎麼來求得該區間的最值(這裡以求最大者舉例).
先求出滿足2^k<=(R-L+1)的最大的k值, 可以這樣求k值: k = log(R-L+1.0)/log(2.0)
這樣就可以把區間劃分成兩個長度為2^k的區間[L, L+2^k-1]和[R-2^k+1, R],
由於前面求的k值是滿足2^k<=(R-L+1)條件的最大值, 所有可以證得L+2^k-1 >= R-2^k+1
而dp[L][k] = max[L, L+2^k-1], dp[R-(1<<k)+1][k] = max[R-2^k+1, R]
這樣, 區間[L, R]的最大值ret = max(dp[L][k], dp[R-(1<<k)+1][k])
(3).解釋"由此我們要注意的是預先處理f(i,j)中的j值只需要計算log(n+1)/log(2)即可,而i值我們也只需要計算到n-2^k+1即可。"這句話。
* 求dp的整個過程中i的最大值為: maxi = max(L, R-2^k+1), 由2^k<=(R-L+1) -> L <= R-2^k+1, 所以對於i, 只需要計算到R-2^k+1即可.
* 同理可推出j的計算範圍: log(R-L+1)/log(2)取得最大值為L=0時, 即: log(R+1)/log(2)
ps: 個人想法, 僅供參考!!!
練習:
poj 3264 Balanced Lineup
http://162.105.81.212/JudgeOnline/problem?id=3264
#include<cmath><br />#include<iostream><br />using namespace std;<br />#define max(a,b) a>b?a:b<br />#define min(a,b) a>b?b:a<br />#define MAX 50001<br />int a[MAX], dpmax[MAX][16], dpmin[MAX][16];<br />void RMQ(int N)<br />{<br />int i, j;<br />for(i=1; i<=N; i++)<br />{<br />dpmax[i][0] = a[i];<br />dpmin[i][0] = a[i];<br />}<br />//max<br />for(j=1; j<=log(N+1.0)/log(2.0); j++)<br />for(i=1; i<=N-(1<<j)+1; i++)<br />dpmax[i][j] = max(dpmax[i][j-1], dpmax[i+(1<<(j-1))][j-1]);<br />//min<br />for(j=1; j<=log(N+1.0)/log(2.0); j++)<br />for(i=1; i<=N-(1<<j)+1; i++)<br />dpmin[i][j] = min(dpmin[i][j-1], dpmin[i+(1<<(j-1))][j-1]);<br />}</p><p>int main()<br />{<br />//freopen("in.txt", "r", stdin);<br />int N, Q, i, j, k, aa, bb;<br />while(scanf("%d%d", &N, &Q) != EOF)<br />{<br />for(i=1; i<=N; i++)<br />scanf("%d", &a[i]);<br />RMQ(N);<br />while(Q--)<br />{<br />scanf("%d%d", &i, &j);<br />k = log(j - i + 1.0) / log(2.0);<br />aa = max(dpmax[i][k], dpmax[j-(1<<k)+1][k]);<br />bb = min(dpmin[i][k], dpmin[j-(1<<k)+1][k]);<br />printf("%d/n", aa - bb);<br />}<br />}<br />return 0;<br />}