ST表:解决区间最大值问题

2 篇文章 0 订阅
2 篇文章 0 订阅

ST表(Sparse Table,稀疏表)

ST表是一种基于倍增思想,用于解决可重复贡献问题的数据结构。

ST表应用最广泛的领域便是解决RMQ问题(区间最值查询):

给定n个数,m个询问,对于每个询问,需要回答区间[l,r]中的最值。

以最大值为例,ST表使用一个二维数组st[maxn][log(maxn)],对于范围内的所有st[i][j],先进行预处理,计算并存储

max(Ak),k\epsilon [i,i+2j)

本文中区间都是离散意义下的,只包含整数,所以也可以写成:

[i,i+2^j-1]

查询时,再利用这些子区间算出待求区间的最大值。

预处理时间复杂度为O(nlogn),查询时间为O(1)。


ST表原理

st[i][j]表示区间[i,i+2^j-1]中的最小值(显然这是下标范围)

将区间分为前后两端,则任意一段的最大值显然等于

max(前半段的最大值,后半段的最大值)

我们来推导一下st[i][j]的状态转移方程:

ii+2^j-1的长度为2^j​​,那么一半的长度为2^j/22^{j-1}

那么前半段最值的区间[i,i+2^{j-1}-1]

状态表示为:st[i][j-1]

后半段区间为[i+2^{j-1},i+2^j-1],右边式子改写为(i+2^{j-1})+2^{j-1}-1

状态表示为:st[i+2^{j-1}][j-1]

所以st[i][j] = max(st[i][j-1],st[i+2^{j-1}][j-1])

采用位运算的方式进行处理,提高效率:

int st[maxn][30] //列数一般不小于log2(maxn)即可
for(int i = 1; i <= n; i++){
  st[i][0] = read();//读入数据
}
for(int j = 1; j <= 30; i++){
  for(int i = 1; i + (1 << j) - 1 <= n; i++){
     st[i][j] = max(st[i][j - 1],st[i + (1 << (j - 1))][j - 1]);
   } 
}

左边子区间取最大值,右边子区间取最大值,再取两个子区间的最大值


 查询

查询时,需要找到目标区间[l,r][l,r][l,r]的两个子区间,使他们的并集为[l,r](不必不相交)。具体地,就是要找到一个整数s,两个子区间分别为[l, l+2^s-1][r-2^s+1,r]

 我们希望前一个子区间的右端点尽可能接近r(同样会使第二个子区间的左端点尽可能接近l)。当l+2^s-1 = r时,有s = log2(r-l+1)

由于s是整数,我们取s = [log2(r-l+1)]。可以证明,此时两个子区间确实可以覆盖[l,r]

//计算s:
int s = log(r - l + 1) / log 2;

 每次计算log值太花时间了,我们同样可以进行一次递推的预处理:

int logn[maxn] = {-1};
for(int i = 1; i <= n; i++){
    logn[i] = logn[i / 2]  + 1;
}

 在线查询的代码如:

int queMax(int l, int r){
    int k = logn[r - l + 1];
    return max(st[l][k], st[r - (1<<k) + 1][k]);
}

洛谷模板题: 【模板】ST 表 - 洛谷

给出一道模板题进行练习,下面是我的AC代码

#include <iostream>
#include <string> 
#include <cmath>
using namespace std;

inline int read(){
	int x = 0, f = 1; 
	char ch = getchar();
	while(ch < '0' || ch > '9'){
		if(ch == '-')
			f = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9'){
		x = x * 10 + ch - 48;
		ch = getchar();
	}
	return x * f;
}

long long n, m;
const int maxn = 1e5 + 9;
long long st[maxn][20];
long long logn[maxn] = {-1};
long long l, r;

long long queMax(long long l, long long r){
	int k = logn[r - l + 1];
	return max(st[l][k], st[r - (1<<k) + 1][k]);
}

int main(){
	n = read();
	m = read();
	for(long long i = 1; i <= n; i++){
		st[i][0] = read();
		logn[i] = logn[i / 2] + 1;
	}
	for(long long j = 1; j <= 20; j++){
		for(long long i = 1; i + (1 << j) - 1 <= n; i++){
			st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
		}
	}
	for(long long i = 1; i <= m; i++){
		l = read();
		r = read();
		cout<<queMax(l,r)<<'\n';
	}
}

ST表优点:时间复杂度较低 、代码量较少。

ST表缺点:维护的信息有限,只支持静态操作(不支持修改操作)。


可重复贡献问题

凡是符合结合律可重复贡献的信息查询都可以使用ST表高效进行。

可重复贡献问题是对于一个二元运算opt,运算的性质满足

x o p t x = x

例如:最大值满足max(x,x) = x,最大公因数gcd(x,x) = x

显然最大值、最小值、最大公因数、最小公倍数、按位或、按位与都符合这个条件。可重复贡献的意义在于,可以对两个交集不为空的区间进行信息合并。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值