ST表用法详解

​一、ST表

ST 表是用于解决 可重复贡献问题 的数据结构。

1.可重复贡献问题

指对于运算 o p t opt opt,满足 x x x p o t pot pot x = x x=x x=x,则对应的区间询问就是一个可重复贡献问题。例如,最大值有 m a x ( x , x ) = x max(x,x)=x max(x,x)=x,gcd有 g c d ( x , x ) = x gcd(x,x)=x gcd(x,x)=x,所以RMQ和区间GCD就是一个可重复贡献问题。但区间和就不具有这个性质。

2.简介

基于动态规划思想。最基础的应用是RMQ:有n个数,m个询问。每次讯问中,给定区间 [ l , r ] [l,r] [l,r],求区间最大值和最小值。
时间复杂度: O ( n O(n O(n l o g log log n ) n) n)预处理, O ( 1 ) O(1) O(1)查询。

二、实现

1.创建

定义一个二维数组f,其中 f [ i ] [ j ] f[i][j] f[i][j]表示从i开始,长度为 2 j 2^j 2j的区间的最大值(或其他需要查询的值)。首先将 f [ i ] [ 0 ] f[i][0] f[i][0]初始化为原始数组中的元素值。然后,对于任何一个长度为 2 j 2^j 2j的区间,我们都可以将其分解为两个长度为 2 ( j − 1 ) 2(j-1) 2(j1)的子区间。这两个子区间的起点分别是i和i+2^(j-1)。然后,我们可以得到如下方程:
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + ( 1 < < j − 1 ) ] [ j − 1 ] ) f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]) f[i][j]=max(f[i][j1],f[i+(1<<j1)][j1])

附上代码

for(ll j=1;j<=log2(n);j++){
	for(ll i=1;i+(1<<j)-1<=n;i++){
		f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
	}
}

2.查询

对于一个长度为len的区间[l, r],我们可以找到最大的k使得 2 k < = l e n 2^k<= len 2k<=len,然后返回 m a x ( f [ l ] [ k ] , f [ r − 2 k + 1 ] [ k ] ) max(f[l][k], f[r-2^k+1][k]) max(f[l][k],f[r2k+1][k])即可。

附上代码

ll k,maxx;
k=log2(r-l+1);
maxx=max(f[l][k],f[r-qpow(2,k)+1][k]);
return maxx;

三、应用

1.题目:平衡的阵容

题目描述

农夫 J o h n John John n n n头牛和各自的身高。他准备了 q q q组数据,想知道每一组里面最高和最低的牛的身高差。

输入格式

第一行两个数 n n n, q q q。接下来 n n n行,每行一个数 h i ​ h_i​ hi。再接下来 q q q行,每行两个整数 a a a b b b,表示询问第 a a a头牛到第 b b b头牛里的最高和最低的牛的身高差。

输出格式

输出共 q q q行,对于每一组询问,输出每一组中最高和最低的牛的身高差。

2.讲解

ST表模板。只是要同时求最大值和最小值。

预处理

通过build函数,我们使用了两个二维数组max_f和min_f来存储所有可能的区间的最大值和最小值。对于每一个长度为 2 j 2^j 2j的区间,我们计算并存储这个区间内的最大值和最小值。

查询

通过find函数,我们使用了预处理得到的数据来快速查询任意区间的最大值和最小值。给定一个查询区间(从 l l l r r r),我们首先找到最大的 j j j使得长度为 2 j 2^j 2j的区间可以完全包含在查询区间内。然后,我们比较两个长度为 2 j 2^j 2j的区间的最大值,这两个区间分别是从 l l l开始和以 r r r结束的区间。

主函数

在主函数中,我们首先读入牛的数量和查询的数量,并初始化数组。然后,我们调用预处理函数来计算所有可能区间的最大值。最后,对于每一个查询,我们调用查询函数并输出结果。

3.AC代码

#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
using namespace std;
ll n,a[200002],max_f[200002][40],min_f[200002][40],q;
ll qpow(ll x,ll y){
	ll ans=1;
	while(y>0){
		if(y&1==1){
			ans=(ans*x);
		}
		x=(x*x);
		y>>=1;
	}
	return ans;
}
void build(){
	for(ll j=1;j<=log2(n);j++){
		for(ll i=1;i+(1<<j)-1<=n;i++){
			max_f[i][j]=max(max_f[i][j-1],max_f[i+(1<<j-1)][j-1]);
			min_f[i][j]=min(min_f[i][j-1],min_f[i+(1<<j-1)][j-1]);
		}
	}
}
ll find(ll l,ll r){
	ll k,ans_max,ans_min;
	k=log2(r-l+1);
	ans_max=max(max_f[l][k],max_f[r-qpow(2,k)+1][k]);
	ans_min=min(min_f[l][k],min_f[r-qpow(2,k)+1][k]);
	return ans_max-ans_min;
}
int main(){
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		max_f[i][0]=min_f[i][0]=a[i];
	}
	build();
	while(q--){
		ll l,r;
		scanf("%lld%lld",&l,&r);
		printf("%lld",find(l,r));pr;
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值