数据结构——ST表

引入

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

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

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

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

算法思想

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

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

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

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

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

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

以最大值为例,设f [ i ] [ j ] f[i][j]f[i][j]表示整个数列A AA中下标在子区间[ i , i + 2 j − 1 ] [i,i+2^j-1][i,i+2j1]里的数的最大值,也就是从i ii开始的2 j 2^j2j个数的最大值。递推边界显然是f [ i ] [ 0 ] = A [ i ] f[i][0]=A[i]f[i][0]=A[i],即数列A AA在区间[ 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 kk,满足:
2 k ≤ r − l + 1 < 2 k + 1 2^k \le r-l+1 \lt 2^{k+1}2krl+1<2k+1
也就是2 22k kk次幂小于区间长度的前提下的最大的k kk。我们对上诉不等式进行求解:

先看左边:
2 k ≤ r − l + 1 2^k \le r-l+12krl+1
两边同时取l o g 2 log_2log2为底的对数:
k ≤ l o g 2 ( r − l + 1 ) k\le log_2(r-l+1)klog2(rl+1)
进行换底公式(C++中l o g loglog的底数为自然对数e ee
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}-1k>log2log(rl+1)1
由此可见,最大的k kk就是k kk的上界,因此区间[ 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)查询了。


版权声明:本文为Lucas_FC_原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。