定义:
RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:
对于长度为n的数列A,回答若干询问RMQ(A,i,j) (i,j<=n),返回数列A中下标在i,j之间的最小/大值。
此类问题的解决方法有很多,暴力(当然也就说说,基本没有出题的会让你暴过去)、线段树(复杂度为O(nlogn))、以及一个非常有名的在线处理RMQ问题的算法 -- Sparse_Table算法,简称ST算法,该算法能在进行O(nlogn)的预处理后达到查询O(1)的效率。主要思想为dp。
(一)预处理,DP
设A[i]是要求区间最值的数列,dp[i][j]表示从第i个数起连续2j个数中的最大值。
例如有A[10] = 3 2 4 5 6 8 1 2 9 7;
初值:
dp[1][0]表示第1个数起,长度为20=1的最大值,即元素3。同理 F[1][1] = max(3,2) = 3, F[1][2]=max(3,2,4,5) = 5,F[1][3] = max(3,2,4,5,6,8,1,2) = 8;
并且我们可以容易的看出dp[i][0] = A[i]。
状态转移方程:
我们把dp[i][j]分成长度相同且为2j-1的两段,一段为dp[i][2j - 1], 一段为dp[i+2j - 1][2j - 1]。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。
dp[i,j]就是这两段各自最大值中的最大值。于是我们得到了状态转移方程:
dp[i][j] = max(dp[i][j-1], dp[i + 2(j-1)][j-1];
1 /*预处理->O(nlogn)*/ 2 void initRMQ(int n) 3 { 4 for(int i = 1; i <= n; i++) dp[i][0] = A[i]; 5 for(int j = 1; j < 20; i++) 6 for(int i = 1; i <= n; j++) 7 dp[i][j] = max(dp[i][j-1], dp[i + (1 << (j-1))][j-1]); 8 }
注意代码中循环的顺序,外层是j,内层是i!!
(二)查询
若查询区间为A[i, j],那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可重叠,比如查询A[5,9],我们可以查询A[5678]和A[6789])。
因为这个区间的长度len = j - i + 1,所以我们可以取k=log2(len),则有:RMQ(A, i, j) = max{ dp[i][k], dp[j-2k+1][k] }。
例如,查询区间[2,8]的最大值,k = log2(8 - 2 + 1) = 2,即求max( dp[2][2],dp[8 - 2 ^ 2 + 1][2]) = max(F[2, 2],F[5, 2]);
int Log2[maxn]; void init() { Log2[0] = -1; Log2[1] = 0; int i, j; for(i = 2, j = 0; i < maxn; i++) { if(i > (1<<(j+1))) j++; Log2[i] = j; } } int query(int L, int R) { if(L > R) swap(L, R); if(L == R) return dp[L][0]; int len = R-L+1; int k = Log[len]; //此处用的是Log2[]数组保存每个数的log值,当然也可以用下面的式子求得; //int k = (int)(log(des - src + 1.0) / log(2.0)); return max(dp[L][k], dp[R-(1<<k)+1][k]); }