RMQ问题——ST表

RMQ问题

定义(来自百度百科)

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

例题

n n n个数, m m m次询问,每次询问 a [ i ] a[i] a[i] a [ j ] a[j] a[j]中的最大(小)值。

主要实现方法
  • 朴素的暴力算法
for(int i=1;i<=m;i++){
    int l,r,maxn=-INF;
    cin>>l>>r;
    for(int j=l;j=r;j++){
   	    maxn=max(maxn,a[j]);
    }
    cout<<maxn<<endl;
}

优点:实现简单
缺点:单次查询复杂度最坏 O ( n ) O(n) O(n),总复杂度最坏为 O ( m n ) O(mn) O(mn),会超时

  • 线段树等高级数据结构

代码略
优点:可以支持修改, O ( n l o g n ) O(nlogn) O(nlogn)不会超时
缺点:实现复杂

  • ST表

优点:实现简单, O ( n l o n g n ) O(nlongn) O(nlongn)预处理, O ( 1 ) O(1) O(1)查询
缺点:无法支持修改

ST表实现

当n= 1 0 6 10^6 106时,线段树等 O ( n l o g n ) O(nlogn) O(nlogn)级别的数据结构就有一点力不从心,有超时的风险,所以我们需要一种查询为 O ( 1 ) O(1) O(1)级别的数据结构。
容易想到把每个区间的最值存在一个数组里,每次查询时直接取出。
但这样不但空间存不下,如何 O ( n l o g n ) O(nlogn) O(nlogn)预处理也是一个难题。

在这里,我们需要用到一种动态规划的思想。

若将一个区间分成两部分, 1 1 1 − - a a a ( a + 1 ) (a+1) (a+1) − - n n n,则最大值 M a x = m a x ( M a x n 1 − a , M a x n ( a + 1 ) − n Max=max(Maxn_{1-a},Maxn_{(a+1)-n} Max=max(Maxn1aMaxn(a+1)n中的最大值。
为了使时间复杂度均分, a a a应取到该区间的中点。
f [ i ] [ j ] f[i][j] f[i][j]为区间 i i i − - i + 2 j i+2^j i+2j的最值。
则有转移方程式 f [ i ] [ j ] f[i][j] f[i][j]= m a x max max ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) (f[i][j-1],f[i+2^{j-1}][j-1]) (f[i][j1],f[i+2j1][j1])
边界条件 f [ i ] [ 0 ] = a [ i ] f[i][0]=a[i] f[i][0]=a[i]

然后就可以在 O ( n l o g n ) O(nlogn) O(nlogn)时间里预处理出所有的值了
ST表查询

设给定区间为 L , R L,R L,R,则将 L , R L,R L,R分成两段能够覆盖完全整个区间的区间。
但为了查询方便,我们需要契合预处理的数组,将其分为 L — — L + 2 l o g ( l e n ) L——L+2^{log(len)} LL+2log(len) R − 2 l o g ( l e n ) — — R R-2^{log(len)}——R R2log(len)R两个区间,然后即可求出最大值。

细节

由于c++中 l o g log log函数的效率不高,所以我们可以花 O ( n ) O(n) O(n)时间来求出每个数对应的 l o g log log值,方法为 l o g [ n ] = l o g [ n / 2 ] + 1 log[n]=log[n/2]+1 log[n]=log[n/2]+1

代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int RMQ[100005][25],n,m,x,y,Log[100005];
void ST()
{
	for(int j=1;(1<<j)<=n;j++)
	{
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			RMQ[i][j]=max(RMQ[i][j-1],RMQ[i+(1<<(j-1))][j-1]);
		}
	}
}
int ask(int L,int R)
{
	int k=Log[R-L+1];
	//int k=0;
	//while((1<<(k+1))<R-L+1)  k++;
	//k=(int)(log(R-L+1)/log(2));
	return max(RMQ[L][k],RMQ[R-(1<<k)+1][k]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&RMQ[i][0]);
	}
	for(int i=1;i<=n;i++){
		Log[i]=Log[i/2]+1;
	}
	ST();
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",ask(x,y));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值