基础数据结构——ST表

ST表
引入:

给定一长度为 n n n的序列 n u m i num_i numi,有 Q Q Q次询问.对于每次询问,给定 l l l r r r,求区间 A l t o A r A_l to A_r AltoAr的最值.

这类无修改操作的静态区间最值问题称为 R M Q RMQ RMQ问题.不妨以求最大值为例,一种朴素的解法是,每次一一枚举区间 [ l , r ] [l,r] [l,r],逐个比较并更新区间最值.但是这种解法单次查询复杂度为 O ( n ) \Omicron(n) O(n),效率极低.为此我们引入 S T ST ST

ST表:

S T ST ST一般用于解决静态运算可重复覆盖的问题.静态指无修改操作,运算可重复覆盖指参与运算的一个成员重复多次相同计算不会影响最终结果,类似于自反性.例如: m a x ( a , a ) = a max(a,a)=a max(a,a)=a.由于 R M Q RMQ RMQ问题恰好符合如上特征,因此 S T ST ST表十分适合用来解决此类问题.

S T ST ST表的核心思想在于倍增,优势是经过 O ( n l o g n ) \Omicron(nlogn) O(nlogn)的预处理,可以做到 O ( 1 ) \Omicron(1) O(1)查询,且常数极低.下面讨论 S T ST ST表的实现过程:

首先是建表.以 R M Q RMQ RMQ问题为例,我们建立数组 r m x [ i ] [ k ] rmx[i][k] rmx[i][k]表示从第 i i i个数开始向右(包含第 i i i个数) 2 k 2^k 2k个数的最大值.

对于 k = 0 k=0 k=0的情况,显然有 r m x [ i ] [ 0 ] = n u m [ i ] rmx[i][0]=num[i] rmx[i][0]=num[i]

k > 0 k>0 k>0时,由于 2 k = 2 k − 1 + 2 k − 1 2^k=2^{k-1}+2^{k-1} 2k=2k1+2k1,因此 r m x [ i ] [ k ] = m a x i ≤ j ≤ i + 2 k − 1 { n u m [ j ] } = m a x ( m a x i ≤ j ≤ i + 2 k − 1 − 1 { n u m j } , m a x i + 2 k − 1 ≤ j ≤ i + 2 k − 1 { n u m j } ) rmx[i][k]=max_{i\leq j\leq i+2^k-1}\{num[j] \}=max(max_{i\leq j\leq i+2^{k-1}-1 }\{num_j\} ,max_{i+2^{k-1}\leq j\leq i+2^k-1}\{num_j\}) rmx[i][k]=maxiji+2k1{num[j]}=max(maxiji+2k11{numj},maxi+2k1ji+2k1{numj})

因此转移方程可以写为 r m x [ i ] [ k ] = m a x ( r m x [ i ] [ k − 1 ] + r m x [ i + ( 1 < < ( k − 1 ) ) ] [ k − 1 ] ) rmx[i][k]=max(rmx[i][k-1]+rmx[i+(1<<(k-1))][k-1]) rmx[i][k]=max(rmx[i][k1]+rmx[i+(1<<(k1))][k1])

预处理代码如下:

	for (R ll i=1; i<=n; i++) mxr[i][0]=num[i];
	for (R ll k=1; k<=20; k++) {
		for (R ll i=1; i<=n; i++) {
			mxr[i][k]=mxr[i][k-1];
			if (i+(1<<(k-1))<=n) mxr[i][k]=max(mxr[i][k], mxr[i+(1<<(k-1))][k-1]);
		}
	}   

按照相同方法倒序进行倍增,可以求出 l m x [ i ] [ k ] lmx[i][k] lmx[i][k].

对于每次询问给出区间 [ l , r ] [l,r] [l,r],区间长为 l e n = r − l + 1 len=r-l+1 len=rl+1.我们只需要找到一个最大的 k k k,使得 2 k ≤ l e n 2^k\leq len 2klen,由于 m a x max max操作满足重复运算可覆盖性,因此答案显然就是 m a x ( r m x [ l ] [ k ] , l m x [ r ] [ k ] ) max(rmx[l][k], lmx[r][k]) max(rmx[l][k],lmx[r][k]) 由于每个 l e n len len对应一个特定的 k k k,因此只需要进行预处理求出即可.详见完整代码.

code
#define chkmax(x, y) (x=max(x, y))
const ll N=1e5+5;

ll n, m;
ll num[N];
ll lmx[N][20], rmx[N][20];
ll p[N];
int main() {
	read(n); read(m);
	for (R ll i=1, j=0; i<=n; i++) {
		read(num[i]);
		p[i]=j;
		if (i==(1<<(j+1))) ++j;
		lmx[i][0]=rmx[i][0]=num[i];
	}
	ll mx=p[n]+1;
	for (R ll k=1; k<mx; k++) {
		for (R ll i=1; i<=n; i++) {
			rmx[i][k]=rmx[i][k-1];
			if (i+(1<<k-1)<=n) chkmax(rmx[i][k], rmx[i+(1<<(k-1))][k-1]);
		}
		for (R ll i=n; i; i--) {
			lmx[i][k]=lmx[i][k-1];
			if (i-(1<<k-1)>0) chkmax(lmx[i][k], lmx[i-(1<<(k-1))][k-1]);
		}
	}
	ll l, r, len;
	while (m--) {
		read(l); read(r); len=r-l+1;
		writeln(max(rmx[l][p[len]], lmx[r][p[len]]));
	}
}
扩展:

如果操作不满足重复运算可覆盖性,如加法运算,是否依然可以用 S T ST ST表处理呢?

答案是可以的,只不过查询复杂度会变为 O ( l o g n ) \Omicron(logn) O(logn)

[ l , r ] [l,r] [l,r]区间和,我们可以预处理出区间和 s u m [ i ] [ k ] sum[i][k] sum[i][k]表示区间 [ i , i + 2 k − 1 ] [i,i+2^k-1] [i,i+2k1]的和.对于每次查询,区间长为 l e n = r − l + 1 len=r-l+1 len=rl+1,可以将 l e n len len进行二进制拆分为 l e n = 2 k 1 + 2 k 2 + . . . + 2 k c len=2^{k_1}+2^{k_2}+...+2^{k_c} len=2k1+2k2+...+2kc,然后往右跳跃即可.参考下面代码理解一下:

inline ll get_sum(ll l, ll r) {
	ll len=r-l+1, res=0;
	for (R ll k=0; k<=20; k++) {
		if (len>>k&1) res+=sum[l][k], l+=(1<<k);
	}
	return res;
}

这里只是介绍下倍增的跳跃性用法.当然,我们可以用 O ( n ) \Omicron(n) O(n)预处理前缀和 S [ n ] S[n] S[n],然后 O ( 1 ) \Omicron(1) O(1)查询 r e s = s u m [ r ] − s u m [ l − 1 ] res=sum[r]-sum[l-1] res=sum[r]sum[l1],这里的 S T ST ST表显然是大材小用了.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值