RMQ算法小结(ST表)

简介

RMQ ( Range Minimum / Maximum Query ) 问题是指:对于长度为 n 的数列 A,回答若干询问 RMQ (A , i , j ) ( i , j ≤ n),返回数列A中下标在 i , j 里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

关于RMQ问题,还是有很多方法来求解的(像线段树啊什么的),这里主要介绍一下ST算法

要注意的是,ST算法只适用于静态区间求最值,如果是动态的,那还是乖乖打线段树吧
【基本思想】
ST算法它的本质相当于是动态规划,下面我们以求最大值为例(最小值求法和最大值差不多)

我们用 f [ i ][ j ] 表示以 i 为起点,连续 2^j 个数中的最大值,例如 f [ 2 ][ 2 ] 就表示第 2 个数到第 5 个数的最大值

(1)预处理:

我们用A表示原序列,由于 = 1,按照 f 数组的定义,f [ i ][ 0 ] 就等于 A[ 0 ](初始化)

对于其他的处理,我们看下图:在这里插入图片描述

对于每一个 f 数组表示的序列,我们都把它拆成两部分,很明显,它的最大值就是这两部分的最大值中较大的那一个

那么转移方程就是:f [ i ][ j ] = max { f [ i ][ j - 1 ] , f [ i + ( 1 << ( j - 1 ) ][ j - 1 ] } ( 1 << j 是位运算,相当于2^j )

具体的代码:

void RMQ()
{
	int i,j;
	for(i=1;i<=n;++i)
	  f[i][0]=a[i];
	for(j=1;(1<<j)<=n;++j)
	  for(i=1;i+(1<<(j-1))<=n;++i)
	    f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

(2)查询:

查询的话我还是画了一个图:

在这里插入图片描述

假设要查询的区间为 [ l , r ],我们用 L 表示区间 [ l , r ] 的长度,即 L = r - l + 1,下面用 k 表示 log L

其中查询的话,区间长度 L 不一定刚好是 2 的多少次方,又因为 log L 是向下取整,那么 2^k 就有可能小于 L,这样的话,我们就不能直接用 f [ l ][ k ] 来表示答案,不然的话会有遗漏(例如上图中的左半部分)

正确的做法是我们就从 l 往右取 2^k 个(即 f [ l ][ k ]),从 r 往左取 2^k 个(即 f [ r - ( 1 << k ) + 1 ][ k ]),这样就能保证区间 [ l , r ] 都被访问到了,重复计算的不用担心,这是计算最值而不是求和

那么答案 answer = max { f [ l ][ k ] , f [ r - ( 1 << k ) + 1 ][ k ] }

具体的代码如下(Log 数组是预处理出来了的):

for(i=1;i<=询问数;++i)
{
	scanf("%d%d",&l,&r);
	k=Log[r-l+1];
	ans=max(f[l][k],f[r-(1<<k)+1][k]);
	printf("%d\n",ans);
}

(3)预处理 Log 数组:

这里注意两点:

1、Log 数组是向下取整

2、Log[ i ] = Log[ i / 2 ] +1

下面是代码:

void GetLog()
{
	int i;
	Log[1]=0;
	for(i=2;i<=n+1;++i)
	  Log[i]=Log[i/2]+1;
}

【复杂度分析】

其实时间复杂度看代码很容易可以分析出来

预处理部分:j 循环是O(log n),i 循环是O(n),总共是O(n * log n)

询问部分:每次询问的复杂度是O(1),有 q 个询问就是O(q)

【例题】

例题传送门ST表

裸的ST模板啦,代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100010],l,r,k,ans,f[100010][25],Log[100010];
void RMQ()//预处理 
{
	for(int i=1;i<=n;i++) f[i][0]=a[i];//初始化 
	for(int j=1;(1<<j)<=n;j++)
	   for(int i=1;i+(1<<(j-1))<=n;i++)
	      f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	      //(1<<j是位运算,相当于2^j) 
}
void getLog()
{
	int i;
	Log[1]=0;
	for(int i=2;i<=n+1;i++) Log[i]=Log[i/2]+1;
}
int main()
{
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	getLog();
	RMQ();
	for(int i=1;i<=m;i++)//m是查询数 
	{
		scanf("%d%d",&l,&r);
		k=Log[r-l+1];
		ans=max(f[l][k],f[r-(1<<k)+1][k]);
		printf("%d\n",ans);
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,我不确定您指的是哪种RMQ算法。一般来说,RMQ是“区间最小值查询”(Range Minimum Query)的缩写,其实现算法有多种。以下是两种常见的RMQ算法的实现代码,供参考: 1. 线段树RMQ算法 ```python class SegmentTree: def __init__(self, arr): self.tree = [0] * (4 * len(arr)) self.build(arr, 0, 0, len(arr) - 1) def build(self, arr, index, left, right): if left == right: self.tree[index] = arr[left] else: mid = (left + right) // 2 self.build(arr, index * 2 + 1, left, mid) self.build(arr, index * 2 + 2, mid + 1, right) self.tree[index] = min(self.tree[index * 2 + 1], self.tree[index * 2 + 2]) def query(self, index, left, right, qleft, qright): if left > qright or right < qleft: return float('inf') elif qleft <= left and qright >= right: return self.tree[index] else: mid = (left + right) // 2 return min(self.query(index * 2 + 1, left, mid, qleft, qright), self.query(index * 2 + 2, mid + 1, right, qleft, qright)) # 示例 arr = [1, 3, 2, 7, 9, 11] tree = SegmentTree(arr) print(tree.query(0, 0, len(arr) - 1, 1, 4)) # 输出2,即arr[2:5]的最小值 ``` 2. ST算法 ```python import math class ST: def __init__(self, arr): n = len(arr) k = int(math.log2(n)) self.table = [[0] * (k + 1) for _ in range(n)] for i in range(n): self.table[i][0] = arr[i] for j in range(1, k + 1): for i in range(n - 2 ** j + 1): self.table[i][j] = min(self.table[i][j - 1], self.table[i + 2 ** (j - 1)][j - 1]) def query(self, left, right): k = int(math.log2(right - left + 1)) return min(self.table[left][k], self.table[right - 2 ** k + 1][k]) # 示例 arr = [1, 3, 2, 7, 9, 11] st = ST(arr) print(st.query(1, 4)) # 输出2,即arr[2:5]的最小值 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值