基础数据结构——ST表

ST表
引入:

给定一长度为nnn的序列numinum_inumi,有QQQ次询问.对于每次询问,给定lllrrr,求区间AltoArA_l to A_rAltoAr的最值.

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

ST表:

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

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

首先是建表.以RMQRMQRMQ问题为例,我们建立数组rmx[i][k]rmx[i][k]rmx[i][k]表示从第iii个数开始向右(包含第iii个数)2k2^k2k个数的最大值.

对于k=0k=0k=0的情况,显然有rmx[i][0]=num[i]rmx[i][0]=num[i]rmx[i][0]=num[i]

k>0k>0k>0时,由于2k=2k−1+2k−12^k=2^{k-1}+2^{k-1}2k=2k1+2k1,因此rmx[i][k]=maxi≤j≤i+2k−1{num[j]}=max(maxi≤j≤i+2k−1−1{numj},maxi+2k−1≤j≤i+2k−1{numj})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})

因此转移方程可以写为rmx[i][k]=max(rmx[i][k−1]+rmx[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]);
		}
	}   

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

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

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]]));
	}
}
扩展:

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

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

[l,r][l,r][l,r]区间和,我们可以预处理出区间和sum[i][k]sum[i][k]sum[i][k]表示区间[i,i+2k−1][i,i+2^k-1][i,i+2k1]的和.对于每次查询,区间长为len=r−l+1len=r-l+1len=rl+1,可以将lenlenlen进行二进制拆分为len=2k1+2k2+...+2kclen=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)查询res=sum[r]−sum[l−1]res=sum[r]-sum[l-1]res=sum[r]sum[l1],这里的STSTST表显然是大材小用了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值