RMQ总结

本文介绍RMQ(区间最值查询)问题,若原数组需修改可用线段树、树状数组解决;若不需修改且查询多,可用ST表解决。详细阐述ST表预处理和查询的思路、状态定义、初始化、状态转移方程及代码实现,还对查询方法进行了证明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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(nlog2(n)) ,对于每一个查询,时间复杂度为 O(1)O(1)O(1)

三、预处理

1. 思路

主要思路为 DP + 递增;

RMQ预处理过程

下面以求最大值为例;

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] 就将其分为两部分,

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

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

思路如图,

RMQ状态转移过程

状态转移方程

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][j1],dp[i+(1<<j1)][j1]}

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

在枚举 jjj 时,由于枚举的是长度,所以需要确保长度 2j2^j2j 不超过总的长度 nnn ; 枚举 iii 时,由于枚举的是起点,所以应确保以 iii 为起点长度为 2j2^j2j 的区间终点 i+2j−1i + 2^j - 1i+2j1 小于等于总长度 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 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=log2(r−l+1)k = log_2(r - l + 1)k=log2(rl+1) ,查询思路如图;

RMQ查询思路

则取以 lll 为起点,长度为 2k2^k2k 的区间的最大值与以 rrr 为终点,长度为 2k2^k2k 的区间的最大值的最大值即可;

2. 实现

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

则只需要比较区间 [l,l+2k−1][l, l + 2^k - 1][l,l+2k1] 与区间 [r−2k+1,r][r - 2^k + 1, r][r2k+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+2k1]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][r2k+1,r]

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

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 \\ r2k+1l+2k1rl2k+122+rl22kk=log2(rl+1)2+rl2(rl+1)rl2(rl)rl0

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

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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值