RMQ(Range Minimum/Maximum Query),是对于长度为 n 的数列 A,回答若干次询问 RMQ(i,j),返回数列 A 中下标在区间 [i,j] 中的最值,即:区间最值查询问题
目前常用于解决 RMQ 问题的方法为 ST 算法(Sparse Table),利用 ST 算法预处理打出的表,称为 ST 表。
对于 RMQ 问题,给出 n 个数 m 次询问,每次询问区间最值,当 m 较小时,使用暴力即可解决,但随着 m 的增大,O(logn) 的的询问处理已经不够,需要 O(1) 的询问。
而 ST 算法可以在 O(nlogn) 时间内进行预处理,然后在 O(1) 时间内回答每个查询,其实际上就是一种动态规划与打表的思想。
要在 O(1) 求出区间的最值,一个很自然的想法是用动态规划处理的方法,用 dp[i][j] 来记录区间 [i,j] 的最大值,这样显然有状态转移方程:dp[i][j]=max(dp[i][j-1],a[j]),但这样预处理是 O(n*n) 的,还能进一步的优化。
max 函数满足一个性质:允许区间重叠,即 max(i,j,k)=max( max(i,j) , max(j,k) ),也就是说,可以由两个较小的有重叠的区间,直接推出一个大区间,从而减少维护的区间数量。
采用倍增的思想,设 A[i] 是要求区间最值的数列,F[i,j] 表示从第 i 个数起连续 2^j 个数中的最大值。(DP的状态)
可以看出 F[i,0] = A[i](DP的初始值)
把 F[i,j] 平均分成两段 ( F[i,j] 一定是偶数个数字),从 i 到 i+2^(j-1)-1 为一段,i+2^(j-1) 到 i+2^j-1 为一段,长度均为 2^(j-1)
于是得到:F[i,j] = max( F[i , j-1] , F[i+2^(j-1) , j-1] )(状态转移方程)
例如:
A 为:3 2 4 5 6 8
F[1,0] 表示第 1 个数起,长度为 2^0=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
假设要查询的区间为 (i,j),那么需要找到覆盖这个闭区间 [i,j] 的最小幂,由于区间长度为 j-i+1,因此可以取 k=log2(j-i+1),则有:RMQ(i, j) = max{ F[i,k] , F[ j-2^k+1,k] }
例:要求区间 [1,5] 的最大值
有:k = log2(5-1+1) = 2
则:RMQ(1,5) = max( F[1,2] , F[5-2^2+1, 2]) = max(F[1,2] , F[2,2])
int dpMax[N][20];
int dpMin[N][20];
int a[N];
void initMax(int n){//初始化最大值查询
for(int i=1;i<=n;i++)
dpMax[i][0]=a[i];
for(int j=1;(1<
一维 RMQ 问题是求一个数列 A 中的最值,而二维 RMQ 问题是求一个 n*m 的矩阵中,某个子矩阵内的最值
设 F[i][j][ii][jj] = x 表示以 (i, j) 为左上角,以 (i+(1<
易知:F[i][j][0][0] = G[i][j](DP的初始值)
假设 F[i][j][ii][jj] 中的 ii 不为 0,那么 F[i][j][ii][jj] = max(F[i][j][ii-1][jj], F[i+(1<
如果 ii 为 0,那么就按 jj 来求,即 F[i][j][ii][jj] = max(F[i][j][ii][jj-1] , F[i][j+(1<<(jj-1))][ii][jj-1]) (状态转移方程)
简单来说,就是将二维问题转变为一维问题求解
对于一个以 (x1,y1) 为左上角,以 (x2,y2) 为右下角的矩形,将其分成四小块,这四小块可能有重合部分,但他们共同构成了目标矩形:
那么,可先求解第一、二小块的最值,第三、四小块的最值,最后再求整体最值
即:
最终结果为:res = max(temp1,temp2)
int G[N][N];
int dpMin[N][N][10][10];
int dpMax[N][N][10][10];
void initRMQ(int n,int m){//对n*m的矩阵初始化RMQ且矩阵下标从1开始
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dpMin[i][j][0][0]=dpMax[i][j][0][0]=G[i][j];
for(int ii=0;(1<