数据结构——ST表

ST表是一种数据结构,适用于解决具有可重复贡献性质的问题,如区间最大值查询。通过预处理可以在O(nlogn)时间内完成,然后以O(1)时间复杂度回答每个询问。算法基于倍增思想,递推过程中利用子区间最大值的合并来覆盖询问区间,从而降低查询时的复杂度。
摘要由CSDN通过智能技术生成

引入

ST表这种数据结构可以用来解决可重复贡献问题

 这里还不得不提一下什么是可重复贡献问题。

前置知识——可重复贡献问题

 可重复贡献问题是对于运算 o p t opt opt,运算的性质满足 x    o p t    x = x x\;opt\;x = x xoptx=x,则对应的区间询问就是一个可重复的贡献问题,例如:最大值满足 m a x ( x , x ) = x max(x,x)=x max(x,x)=x,最大公因数满足 g c d ( x , x ) = x gcd(x,x)=x gcd(x,x)=x,因此 R M Q RMQ RMQ问题和 G C D GCD GCD问题就是一个可重复贡献的问题,但是例如区间和就不满足这个性质,因为在求解区间和的过程中采用的预处理区间会发生重叠,导致重叠部分被重复计算,因此对于 o p t opt opt操作还需要满足结合率才能够使用 S T ST ST表进行求解。

算法思想

 我们先来看一个例子:
  给定 n n n个数,有 m m m个询问,对于每个询问,你需要回答区间 [ l , r ] [l,r] [l,r]中的最大值。
 暴力的算法显然是 O ( n 2 ) O(n^2) O(n2)的,这个算法的效率比较低下。所以我们考虑优化。

 这个时候我们就可以引出 S T ST ST算法了:

S T ST ST表基于倍增思想,可以做到 O ( n l o g n ) O(nlogn) O(nlogn)预处理, O ( 1 ) O(1) O(1)回答每个询问。但是不支持修改操作。所以ST表是一种离线的数据结构

 基于倍增思想,我们考虑如何求出区间最值。可以发现,如果按照一般的倍增流程,每次跳 2 i 2^i 2i步的话,询问时的复杂度仍旧是 O ( l o g n ) O(logn) O(logn) ,并没有比线段树更优,反而预处理一步还比线段树慢。

 我们发现,区间最大值是一个具有“可重复贡献”性质的问题。即使用来求解的预处理区间有重叠部分,只要这些区间的并是所求的区间,最终计算出的答案就是正确的。

 如果手动模拟一下,可以发现我们能使用至多两个预处理过的区间来覆盖询问区间,也就是说询问时的时间复杂度可以被降至 ,在处理有大量询问的题目时十分有效。

 以最大值为例,设 f [ i ] [ j ] f[i][j] f[i][j]表示整个数列 A A A中下标在子区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]里的数的最大值,也就是从 i i i开始的 2 j 2^j 2j个数的最大值。递推边界显然是 f [ i ] [ 0 ] = A [ i ] f[i][0]=A[i] f[i][0]=A[i],即数列 A A A在区间 [ i , i ] [i,i] [i,i]里的最大值。

预处理

 在递推时,我们把子区间的长度成倍增加,于是我们就能轻轻松松得到下面的这个递推表达式:
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1]) f[i][j]=max(f[i][j1],f[i+2j1][j1])

 根据这个思路,我们就可以写出预处理的代码(代码中用到了对数的换底公式)注意代码中的循环顺序一定是先枚举倍增次数,才能确保递推的正确性。

inline void prework(){
	for(int i=1;i<=n;i++)f[i][0]=a[i];
	int t=log(n)/log(2)+1;
	for(int j=1;j<t;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]);
		}
	} 
}

查询

 当查询任意区间 [ l , r ] [l,r] [l,r]的最大值时,我们先计算出一个 k k k,满足:
2 k ≤ r − l + 1 < 2 k + 1 2^k \le r-l+1 \lt 2^{k+1} 2krl+1<2k+1
 也就是 2 2 2 k k k次幂小于区间长度的前提下的最大的 k k k。我们对上诉不等式进行求解:

 先看左边:
2 k ≤ r − l + 1 2^k \le r-l+1 2krl+1
 两边同时取 l o g 2 log_2 log2为底的对数:
k ≤ l o g 2 ( r − l + 1 ) k\le log_2(r-l+1) klog2(rl+1)
 进行换底公式(C++中 l o g log log的底数为自然对数 e e e
k ≤ l o g ( r − l + 1 ) l o g 2 k\le \frac{log(r-l+1)}{log2} klog2log(rl+1)
 再看右边的式子,简单化简:
k > l o g ( r − l + 1 ) l o g 2 − 1 k\gt \frac{log(r-l+1)}{log2}-1 k>log2log(rl+1)1
 由此可见,最大的 k k k就是 k k k的上界,因此区间 [ l , r ] [l,r] [l,r]之间的最大值就是:
m a x ( f [ l ] [ k ] , f [ r − ( 1 < < k ) + 1 ] [ k ] ) max(f[l][k],f[r-(1<<k)+1][k]) max(f[l][k],f[r(1<<k)+1][k])

inline int query(int l,int r){
	int k=log(r-l+1)/log(2);
	return max(f[l][k],f[r-(1<<k)+1][k])
}

 这下就做到了 O ( 1 ) O(1) O(1)查询了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值