RMQ总结

RMQ

一、定义

1. 定义

R M Q RMQ RMQ (Range Minimum / Maximum Query) ,即区间最值查询;

可实现对于长度为 n n n 的数组 a a a ,进行若干次 [ l , r ] [l,r] [l,r] 区间的查询最值问题;

2. 解决方法

若原数组需要修改,则可使用线段树,树状数组解决,查询修改均为 O ( l o g ( n ) ) O(log(n)) O(log(n))

若原数组不需修改,且查询较多,则可使用ST表解决;

二、ST 表

1. 问题类型

ST 表能解决与重复个数无关的问题,即重复计算不会影响的最终答案的问题,如求最大公约数,区间最值等问题;

2. 时间

ST 表预处理时间复杂度为 O ( n ∗ l o g 2 ( n ) ) O(n * log_2(n)) O(nlog2(n)) ,对于每一个查询,时间复杂度为 O ( 1 ) O(1) O(1)

三、预处理

1. 思路

主要思路为 DP + 递增;

RMQ预处理过程

下面以求最大值为例;

2. 状态

求区间的最大值,就先把这个区间分为若干个小区间,分别求到这些小区间的最大值,再将它们合起来求到区间的最大值;而又由于任意一个数都可以表示为若干个 2 的幂次的和 ,所以就将区间拆分成若干个长度为 2 的幂次的小区间,所以定义状态如下:

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从 i i i 位开始,连续 2 j 2^j 2j 个数中的最大值;
d p dp dp 数组的初始大小为 d p [ n ] [ l o g 2 ( n ) ] dp[n][log_2(n)] dp[n][log2(n)]

3. 初始化

i i i 位开始,连续 1 个数中最大即为原数组中的第 i i i 位上的数;

i ∈ [ 1 , n ]    { d p [ i ] [ 0 ] = a [ i ] } i \in [1, n] \; \{ dp[i][0] = a[i] \} i[1,n]{dp[i][0]=a[i]}

4. 状态转移

若要计算 d p [ 2 ] [ 2 ] dp[2][2] dp[2][2] ,即为求从第二个数字开始,连续四个数的最大值,则将这个区间分为两个长度相等相等的区间 [ 2 , 3 ] [2, 3] [2,3] [ 4 , 5 ] [4, 5] [4,5],将这两个区间最大值求最大值,即为 d p [ 2 ] [ 2 ] dp[2][2] dp[2][2] 的值, d p [ 2 ] [ 2 ] = max ⁡ { d p [ 2 ] [ 1 ] , d p [ 4 ] [ 1 ] } dp[2][2] = \max \{ dp[2][1], dp[4][1] \} dp[2][2]=max{dp[2][1],dp[4][1]}

因此,对于计算 d p [ i ] [ j ] dp[i][j] dp[i][j] 就将其分为两部分,

  1. [ i , i + 2 ( j − 1 ) − 1 ] [i, i + 2^{(j - 1)} - 1] [i,i+2(j1)1]

  2. [ i + 2 j − 1 , i + 2 j − 1 ] [i + 2^{j - 1}, i + 2^j - 1] [i+2j1,i+2j1]

思路如图,

RMQ状态转移过程

状态转移方程

d p [ i ] [ j ] = m a x { d p [ i ] [ j − 1 ] , d p [ 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][j1],dp[i+(1<<j1)][j1]}

在求以 i i i 为起点长度为 2 j 2^j 2j 的最大值的答案时,是由两个长度为 2 j − 1 2^{j - 1} 2j1 的答案进行比较得到的。因此在计算长度为 2 j 2^j 2j 的答案时,需要把所有的长度为 2 j − 1 2^{j - 1} 2j1 的答案计算出来,所以,应该先枚举 j j j 再枚举 i i i

在枚举 j j j 时,由于枚举的是长度,所以需要确保长度 2 j 2^j 2j 不超过总的长度 n n n ; 枚举 i i i 时,由于枚举的是起点,所以应确保以 i i i 为起点长度为 2 j 2^j 2j 的区间终点 i + 2 j − 1 i + 2^j - 1 i+2j1 小于等于总长度 n n n ;则,

1 ≤ j ,    2 j ≤ n ; 1 ≤ i ,    i + 2 j − 1 ≤ n 1 \leq j, \; 2^j \leq n; \\ 1 \leq i, \; i + 2^j - 1 \leq n 1j,2jn1i,i+2j1n

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 = l o g 2 ( r − l + 1 ) k = log_2(r - l + 1) k=log2(rl+1) ,查询思路如图;

RMQ查询思路

则取以 l l l 为起点,长度为 2 k 2^k 2k 的区间的最大值与以 r r r 为终点,长度为 2 k 2^k 2k 的区间的最大值的最大值即可;

2. 实现

对于每一次的查询,我们要查询 [ l , r ] [l, r] [l,r] 这个范围的最大值;

则只需要比较区间 [ l , l + 2 k − 1 ] [l, l + 2^k - 1] [l,l+2k1] 与区间 [ r − 2 k + 1 , r ] [r - 2^k + 1, r] [r2k+1,r] 的最大值即可;

a n s = max ⁡ { d p [ l ] [ k ] , d p [ 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. 证明

d p [ l ] [ k ] dp[l][k] dp[l][k] 维护的范围是 [ l , l + 2 k − 1 ] [l, l + 2^k - 1] [l,l+2k1] d p [ r − ( 1 < < k ) + 1 ] [ k ] dp[r - (1 << k) + 1][k] dp[r(1<<k)+1][k] 维护的范围是 [ r − 2 k + 1 , r ] [r - 2^k + 1, r] [r2k+1,r]

所以说,只要能够保证 r − 2 k + 1 < = l + 2 k − 1 r - 2^k + 1 <= l + 2^k - 1 r2k+1<=l+2k1 ,即可保证答案是正确的;

r − 2 k + 1 ≤ l + 2 k − 1 r − l ≤ 2 k + 1 − 2 2 + r − l ≤ 2 ∗ 2 k ∵ k = l o g 2 ( 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 \\ r2k+1l+2k1rl2k+122+rl22kk=log2(rl+1)2+rl2(rl+1)rl2(rl)rl0

又由于 r − l ≥ 0 r - l \geq 0 rl0 是恒成立的,所以原式恒成立;

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;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值