ST算法是解决RMQ(区间最值)问题,它能在O(nlogn)的时间预处理,然后O(1)回答。
虽然我们预处理出来的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)
其原理是倍增,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],它俩的最大值就是这个区间的最大值。
虽然我们预处理出来的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;
}