RMQ
一、定义
1. 定义
RMQRMQRMQ (Range Minimum / Maximum Query) ,即区间最值查询;
可实现对于长度为 nnn 的数组 aaa ,进行若干次 [l,r][l,r][l,r] 区间的查询最值问题;
2. 解决方法
若原数组需要修改,则可使用线段树,树状数组解决,查询修改均为 O(log(n))O(log(n))O(log(n)) ;
若原数组不需修改,且查询较多,则可使用ST表解决;
二、ST 表
1. 问题类型
ST 表能解决与重复个数无关的问题,即重复计算不会影响的最终答案的问题,如求最大公约数,区间最值等问题;
2. 时间
ST 表预处理时间复杂度为 O(n∗log2(n))O(n * log_2(n))O(n∗log2(n)) ,对于每一个查询,时间复杂度为 O(1)O(1)O(1) ;
三、预处理
1. 思路
主要思路为 DP + 递增;
下面以求最大值为例;
2. 状态
求区间的最大值,就先把这个区间分为若干个小区间,分别求到这些小区间的最大值,再将它们合起来求到区间的最大值;而又由于任意一个数都可以表示为若干个 2 的幂次的和 ,所以就将区间拆分成若干个长度为 2 的幂次的小区间,所以定义状态如下:
dp[i][j]dp[i][j]dp[i][j] 表示从 iii 位开始,连续 2j2^j2j 个数中的最大值;
则 dpdpdp 数组的初始大小为 dp[n][log2(n)]dp[n][log_2(n)]dp[n][log2(n)] ;
3. 初始化
从 iii 位开始,连续 1 个数中最大即为原数组中的第 iii 位上的数;
i∈[1,n] {dp[i][0]=a[i]}i \in [1, n] \; \{ dp[i][0] = a[i] \}i∈[1,n]{dp[i][0]=a[i]} ;
4. 状态转移
若要计算 dp[2][2]dp[2][2]dp[2][2] ,即为求从第二个数字开始,连续四个数的最大值,则将这个区间分为两个长度相等相等的区间 [2,3][2, 3][2,3] 与 [4,5][4, 5][4,5],将这两个区间最大值求最大值,即为 dp[2][2]dp[2][2]dp[2][2] 的值, dp[2][2]=max{dp[2][1],dp[4][1]}dp[2][2] = \max \{ dp[2][1], dp[4][1] \}dp[2][2]=max{dp[2][1],dp[4][1]} ;
因此,对于计算 dp[i][j]dp[i][j]dp[i][j] 就将其分为两部分,
-
[i,i+2(j−1)−1][i, i + 2^{(j - 1)} - 1][i,i+2(j−1)−1] ;
-
[i+2j−1,i+2j−1][i + 2^{j - 1}, i + 2^j - 1][i+2j−1,i+2j−1];
思路如图,
状态转移方程
dp[i][j]=max{dp[i][j−1],dp[i+(1<<j−1)][j−1]} dp[i][j] = max\{dp[i][j - 1], dp[i + (1 << j - 1)][j - 1]\} dp[i][j]=max{dp[i][j−1],dp[i+(1<<j−1)][j−1]}
在求以 iii 为起点长度为 2j2^j2j 的最大值的答案时,是由两个长度为 2j−12^{j - 1}2j−1 的答案进行比较得到的。因此在计算长度为 2j2^j2j 的答案时,需要把所有的长度为 2j−12^{j - 1}2j−1 的答案计算出来,所以,应该先枚举 jjj 再枚举 iii ;
在枚举 jjj 时,由于枚举的是长度,所以需要确保长度 2j2^j2j 不超过总的长度 nnn ; 枚举 iii 时,由于枚举的是起点,所以应确保以 iii 为起点长度为 2j2^j2j 的区间终点 i+2j−1i + 2^j - 1i+2j−1 小于等于总长度 nnn ;则,
1≤j, 2j≤n;1≤i, i+2j−1≤n 1 \leq j, \; 2^j \leq n; \\ 1 \leq i, \; i + 2^j - 1 \leq n 1≤j,2j≤n;1≤i,i+2j−1≤n
5. 代码
void init() {
for (int i = 1; i <= n; i++) dp[i][0] = a[i]; // 初始化
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << j - 1)][j - 1]);
}
}
return ;
}
四、查询
1. 思路
令 k=log2(r−l+1)k = log_2(r - l + 1)k=log2(r−l+1) ,查询思路如图;
则取以 lll 为起点,长度为 2k2^k2k 的区间的最大值与以 rrr 为终点,长度为 2k2^k2k 的区间的最大值的最大值即可;
2. 实现
对于每一次的查询,我们要查询 [l,r][l, r][l,r] 这个范围的最大值;
则只需要比较区间 [l,l+2k−1][l, l + 2^k - 1][l,l+2k−1] 与区间 [r−2k+1,r][r - 2^k + 1, r][r−2k+1,r] 的最大值即可;
ans=max{dp[l][k],dp[r−(1<<k)+1][k]} ans = \max \{dp[l][k], dp[r - (1 << k) + 1][k]\} ans=max{dp[l][k],dp[r−(1<<k)+1][k]}
3. 证明
dp[l][k]dp[l][k]dp[l][k] 维护的范围是 [l,l+2k−1][l, l + 2^k - 1][l,l+2k−1] , dp[r−(1<<k)+1][k]dp[r - (1 << k) + 1][k]dp[r−(1<<k)+1][k] 维护的范围是 [r−2k+1,r][r - 2^k + 1, r][r−2k+1,r] ;
所以说,只要能够保证 r−2k+1<=l+2k−1r - 2^k + 1 <= l + 2^k - 1r−2k+1<=l+2k−1 ,即可保证答案是正确的;
r−2k+1≤l+2k−1r−l≤2k+1−22+r−l≤2∗2k∵k=log2(r−l+1)∴2+r−l≤2∗(r−l+1)r−l≤2∗(r−l)r−l≥0 r - 2^k + 1 \leq l + 2^k - 1 \\ r - l \leq 2^{k + 1} - 2 \\ 2 + r - l \leq 2 * 2^k \\ \because k = log_2(r - l + 1) \\ \therefore 2 + r - l \leq 2 * (r - l + 1) \\ r - l \leq 2 * (r - l) \\ r - l \geq 0 \\ r−2k+1≤l+2k−1r−l≤2k+1−22+r−l≤2∗2k∵k=log2(r−l+1)∴2+r−l≤2∗(r−l+1)r−l≤2∗(r−l)r−l≥0
又由于 r−l≥0r - l \geq 0r−l≥0 是恒成立的,所以原式恒成立;
4. 代码
int query (int l, int r) {
int k = log2(r - l + 1);
return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}
五、模板代码
#include <cstdio>
#include <cmath>
#include <algorithm>
#define MAXN 100005
using namespace std;
int n, m, l, r, a[MAXN], dp[MAXN][45];
void init() {
for (int i = 1; i <= n; i++) dp[i][0] = a[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << j - 1)][j - 1]);
}
}
return ;
}
int query (int l, int r) {
int k = log2(r - l + 1);
return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
init();
for (int i = 1; i <= m; i++) {
scanf("%d %d", &l, &r);
printf("%d\n", query(l, r));
}
return 0;
}