RMQ用于区间快速查找最值,适用于期间数值无更改的情况。其预处理的复杂度为O(nlogn),查询的时间复杂度为O(1),对比于线段树的预处理O(nlogn),查询O(logn)来说,在某些情况下有着其独到的优势。
RMQ原理就是在原来的数组上跑一个dp,我们以查询最大值为例,它的状态定义是这样的:
dp[ i ][ j ]:下标从i开始,长度为2^j的区间的最大值。显然dp[ i ][ 0 ]就是下标是i的那个数字本身。
下面给出其转移方程:
dp[ i ][ j ] = max( dp[ i ][ j - 1 ], dp[ i + 2 ^ j ][ j - 1 ] )
对于询问区间[ i ~ j ]的最大值Max:
设k = log( j - i + 1 ) / log( 2 )
Max = max( dp[ i ][ k ], dp[ j - 2 ^ k + 1 ][ k ] )
上述过程的具体代码如下:
#include <cstdio> #include <algorithm> #include <iostream> #include <cstring> #include <queue> #include <ctime> #include <cmath> #include <set> #define eps 1e-10 #define MAXN 500010 #define INF 2*0x3f3f3f3f using namespace std; int num[MAXN], dp[MAXN][30], n, l, r; int pow(int a, int p) { //求a^p这里用了快速幂,实际用应该用一个数组预处理一下 if (p == 0) return 1; int ans = pow(a, p / 2); ans *= ans; if (p % 2) ans *= a; return ans; } int main() { //freopen("in.in", "r", stdin); //freopen("out.out", "w", stdout); scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &num[i]); for (int i = 1; i <= n; i++) //对dp[i][0]进行初始化 dp[i][0] = num[i]; for (int j = 1; pow(2, j) <= n; j++) //上文说的转移方程 for (int i = 1; i + pow(2, j) - 1 <= n; i++) dp[i][j] = max(dp[i][j - 1], dp[i + pow(2, j - 1)][j - 1]); scanf("%d %d", &l, &r); //求区间[l~r]之间的最大值 int k = log(r - l + 1) / log(2); int ans = max(dp[l][k], dp[r - pow(2, k) + 1][k]); printf("ans is : %d\n", ans); return 0; }
RMQ问题在处理LCA中有着巨大的用处,其一种在线方法就是使用dfs+RMQ来求两个子节点的最近公共祖先问题,其大致做法就是按照访问的顺序把每个点的时间戳放入一个数组中,这样u和v的公共祖先就是数组中u和v之间时间戳最小的点,这里可以之间用RMQ在O(1)的时间内得到答案了。