ST算法—介绍

ST算法是解决RMQ(区间最值)问题,它能在O(nlogn)的时间预处理,然后O(1)回答。

其原理是倍增f[i][j]表示从i位起的2^j个数中的最大数,即[i,i+2^j-1]中的最大值,从其定义中可以看出来。


下面直接介绍它的预处理过程。

f[i][0]表示[i,i]中的最大值,只能是a[i],故f[i][0]=a[i]。

对于任意的f[j][i],我们分成两段相等长度的数列来看,[j,j+2^(i-1)-1]和[j+2^(i-1),j+2^i-1],分别对应f[j][i-1]和f[j+(1<<i-1)][i-1]。既然这两段的最大值都知道了,它们又恰好完全地覆盖了[j,j+2^i-1],它俩的最大值就是这个区间的最大值。


查询是O(1)解决的,难道真的可以用一个式子求出区间[l,r]的最大值?没错!
虽然我们预处理出来的f很难恰好覆盖[l,r],但是我们可以用两个f来覆盖[l,r],这个覆盖不要求做到不重,只要不漏就可以了(很好理解吧)。既然可以重复,我们只要控制好不要超出[l,r]就好了。
先确定一个长度2^k,其中k=log2(r-l+1)。这个长度2^k保证小于等于r-l+1,因为k是向下取整的。
接着以l为起始点,往右查询,即f[l][k];再以r为结束点,往左查询,即f[r-(1<<k)+1][k]。(要理解r-(1<<k)+1为什么要加1)

最后,把两者比较一下,其最大值就是[l,r]中的最大值。


例题(洛谷3865 【模板】ST表)

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


代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;


int n,m;
int a[maxn];
int f[maxn][20];//f[i][j]表示从i位起的2^j个数中的最大数,即[i,i+(1<<j)-1] 


void ST_prewoek()
{
	for(int i=1;i<=n;i++) f[i][0]=a[i];
	for(int i=1,imax=log2(n);i<=imax;i++)
		for(int j=1;j+(1<<i)-1<=n;j++)//注意j的右端点为j+(1<<i)-1,-1是因为要包含j自己 
			f[j][i]=max(f[j][i-1],f[j+(1<<i-1)][i-1]);
}


int ST_query(int l,int r)//求[l,r]中的最大值 
{
	int k=log2(r-l+1);//区间长度r-l+1 
	return max(f[l][k],f[r-(1<<k)+1][k]);//第1个区间:[l,l+(1<<k)-1];第2个区间:[r,(1<<k)+1~r]
}


int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	ST_prewoek();
	while(m--)
	{
		int l,r,ans;
		scanf("%d%d",&l,&r);
		ans=ST_query(l,r);
		printf("%d\n",ans);
	}
	return 0;
}


  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值