相关概念
- ST表,又名稀疏表,是一种基于倍增思想,用于解决可重复贡献问题的数据结构
倍增:是一种思想,顾名思义,成倍的增加。例如STL库中的vector,之所以称为动态数组,在不指定大小的情况下,初始化为1个空间,而其空间变化规律为1, 2, 4……当添加一个新元素时,如果空间不够,则空间成倍扩大,如下图所示
可重复贡献问题 是指对于运算op,满足 x op x = x,则对应的区间询问就是一个可重复贡献问题。
例如,最大值有 max(x,x) = x,gcd 有 gcd(x,x) = x,所以 RMQ 和区间 GCD 就是一个可重复贡献问题。
应用场景
- ST表可用于解决RMQ问题,即区间最值问题
RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值
原理(以求最大值为例)
- ST表中定义了一个二维数组f[N][M],f[i][j]表示区间[i, i + 2j + 1]内的最大值
- 根据倍增思想,给出状态转移方程f[i][j] = max(f[i][j - 1], f[i + 2^j - 1^][j - 1])
f[i][j]对应区间[i, i + 2j + 1],长度为2j,若想求该区间内的最大值,则可以将该区间划分为两个等长的区间,长度均为2j / 2 = 2j-1,求每个区间的最大值,再取两者的max即可
由于不同区间有重叠部分,因此从最左侧的区间开始寻找,在后续遍历时会用到前面的结果
代码实现
ST表的创建
首先需要确定最大区间长度,以确定f[i][j]中i和j的取值范围
假设数组的长度为n,则有2k <= n < 2k+1,由此可得k = [log2(n)](以2为底n的对数,向下取整)
void ST_Init()
{
for(int i = 1; i <= n; i ++) f[i][0] = a[i]; //f[i][j],当j为0时,区间长度2^j为0,则区间最大值就是区间的唯一元素a[i]
int k = log2(n); //使用math.h库的函数,计算i和j的取值范围
for(int j = 1; j <= k; j ++) //之前已经计算过区间长度为0的情况,这里区间长度由2^1 到 2^k
{
for(int i = 1; i <= n - (1 << j) + 1; i ++) //确定区间左端点,区间范围为[i, i + 2^j - 1],所以 i + 2^j - 1 <= n
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); //状态转移方程
}
}
时间复杂度O(nlogn)
ST表的查询
对于给定的区间[l, r],首先确定k值,区间长度为r - l + 1,则有2k <= r - l + 1 < 2k+1,再将整个区间分为前2k个数,和后2k个数,分别求出最值,再取二者的max
这里我们会发现,划分的两个区域可能会有重叠部分,如[1, 2, 5, 8, 6]划分为[1, 2, 5]和[5, 8, 6]两个部分,第一个区间最大值为5,第二个区间最大值为8,最终得到的整个区间最大值为8。由此可见,区间重叠对整个区间最大值无影响
int ST_Query(int l, int r)
{
int k = log2(r - l + 1);
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
时间复杂度O(1)
相关题目
总结
ST表可在O(nlogn)的时间内,查询到给定区间的最值,速度非常快。但缺点时,该过程中,整个数组的元素不能发生变化,否则,每发生一次变化,就要进行一次初始化
欢迎大家批评指正!!!