浅谈RMQ

RMQ,即Range Minimum/Maximum Query,区间最大最小问题,通常采取 ST表 求解,下面将结合例题讲解

例题

P3865 模板【ST表】

题目描述

给定一个长度为 N N N的数列,和 M M M次询问,求出每一次询问的区间内数字的最大值。

1 ≤ N ≤ 10 5 , 1 ≤ M ≤ 2 × 10 6 , a i ∈ [ 0 , 10 9 ] , 1 ≤ l i ≤ r i ≤ N 1 \leq N \leq {10}^5, 1 \leq M \leq 2 \times {10}^6, a_i \in [0, {10}^9], 1 \leq l_i \leq r_i \leq N 1N105,1M2×106,ai[0,109],1liriN


这种问题最简单的想法就是对于每个区间遍历一次,求出最大值

但这样的时间复杂度是 O ( N M ) O(NM) O(NM)的,明显会超时

那么我们可以考虑一下怎么优化

发现如果我们可以预处理出每个区间的最大值是不是就可以了

这时候就可以使用ST表

ST表

ST表基于倍增的思想,可以做到在 O ( n l o g n ) O(nlogn) O(nlogn)的时间内预处理出从每个点往后 2 x 2^x 2x的区间内的最大值

f [ i ] [ j ] f[i][j] f[i][j]表示从 i i i往后 2 j − 1 2^j-1 2j1的区间内的最大值,那么转移方程为

f [ i ] [ j ] = m a x { f [ i ] [ j − 1 ] f [ i + 2 j − 1 ] [ j − 1 ] f[i][j]=max\begin{cases}f[i][j-1]\\f[i+2^{j-1}][j-1] \end{cases} f[i][j]=max{f[i][j1]f[i+2j1][j1]

首先这个区间可以拆成连个区间,前一半和后一半,然后再比较这两个区间内的最大值就可以了

同时由这个转移方程哦我们可以看出要求出 f [ i ] [ j ] f[i][j] f[i][j],那么 f [ i ] [ j − 1 ] f[i][j-1] f[i][j1]必须要先求出来,所以循环的时候要先循环 j j j再循环 i i i

那么现在处理出每个区间的最大值,我们就需要在 O ( 1 ) O(1) O(1)的时间内求出任意区间的值

现在有一个区间 [ l , r ] [l,r] [l,r],我们先求出 x = l o g 2 ( r − l + 1 ) x=log_2(r-l+1) x=log2(rl+1)。那么这时候整个区间就可以分成 [ l , l + 2 x − 1 ] [l,l+2^x-1] [l,l+2x1] [ r − 2 x + 1 , r ] [r-2^x+1,r] [r2x+1,r]两个区间,然后就可以根据已经预处理好的 f f f数组在 O ( 1 ) O(1) O(1)的时间里查询

那么可能就有同学会问了,如果说分成的两个区间不能覆盖原区间呢?

也就是说,会不会有 r − 2 x + 1 > l + 2 x − 1 r-2^x+1>l+2^x-1 r2x+1>l+2x1的情况

假设 r − 2 x + 1 > l + 2 x − 1 r-2^x+1>l+2^x-1 r2x+1>l+2x1

那么 r − l + 2 > 2 × 2 x r-l+2>2\times 2^x rl+2>2×2x

又因为 x = l o g 2 ( r − l + 1 ) x=log_2(r-l+1) x=log2(rl+1),也就是 2 x = r − l + 1 2^x=r-l+1 2x=rl+1

所以 r − l + 2 > 2 ∗ ( r − l + 1 ) r-l+2>2*(r-l+1) rl+2>2(rl+1)

r − l > 2 ∗ ( r − l ) r-l>2*(r-l) rl>2(rl)

因为 r − l > 0 r-l>0 rl>0

所以 r − l > 2 ∗ ( r − l ) r-l>2*(r-l) rl>2(rl)不成立

r − 2 x + 1 > l + 2 x − 1 r-2^x+1>l+2^x-1 r2x+1>l+2x1不成立

就说明 r − 2 x + 1 ≤ l + 2 x − 1 r-2^x+1\le l+2^x-1 r2x+1l+2x1

那么分成的两个区间就一定可以覆盖原来的区间,我们可以放心的这么拆区间啦

例题Code

#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
int n,m,l,r,x,f[100005][17];
int read()
{
	int res=0,fh=1;char ch=getchar();
	while (ch<'0'||ch>'9') {if (ch=='-') fh=-1;ch=getchar();}
	while (ch>='0'&&ch<='9') res=res*10+(ch-'0'),ch=getchar();
	return res*fh;
}
int main()
{
	n=read();m=read();
	for (int i=1;i<=n;++i)//f[i][0]就是从i到i+2^0-1内的最大值,就是a[i]
		f[i][0]=read();
	for (int j=1;j<=log2(n);++j)
		for (int i=1;i<=n+1-(1<<j);++i)
			f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);//预处理
	for (int i=1;i<=m;++i)
	{
		l=read();r=read();
		x=log2(r-l+1);
		printf("%d\n",max(f[l][x],f[r+1-(1<<x)][x]));//O(1)求答案
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值