递推关系推导:
长度为
因此:
推导总结:
递推关系可视化:
区间长度:2^j = 2^3 = 8
[i, i+7] = [i, i+3] ∪ [i+4, i+7]
st[i][3] = min(st[i][2], st[i+4][2])
区间长度:2^2 = 4
[i, i+3] = [i, i+1] ∪ [i+2, i+3]
st[i][2] = min(st[i][1], st[i+2][1])
st[i+4][2] = min(st[i+4][1], st[i+6][1])
递推的关键:
查询算法推导:
给定查询区间
计算区间长度:
找到最大的2的幂:
分解查询区间:
重叠证明:
设
查询区间:
左区间:
右区间起点:
右区间:
重叠条件:
即:
由于
查询公式:
其中
时间复杂度:
空间复杂度分析:
ST表需要存储:
为什么要预计算log2?
log2数组定义:
递推公式:
边界条件:
log2数组示例:
i: 1 2 3 4 5 6 7 8 9 10
log2[i]:0 1 1 2 2 2 2 3 3 3
预计算代码:
vector<int> log2(n + 1);
log2[1] = 0;
for (int i = 2; i <= n; i++) {
log2[i] = log2[i / 2] + 1;
}
使用示例:
查询长度为6的区间:
构建示例:
给定数组
第一步:初始化(j=0)
st[i][0] = arr[i]
st[0][0] = 3
st[1][0] = 1
st[2][0] = 4
st[3][0] = 1
st[4][0] = 5
st[5][0] = 9
st[6][0] = 2
st[7][0] = 6
第二步:计算j=1(长度为2)
st[i][1] = min(st[i][0], st[i+1][0])
st[0][1] = min(3, 1) = 1
st[1][1] = min(1, 4) = 1
st[2][1] = min(4, 1) = 1
st[3][1] = min(1, 5) = 1
st[4][1] = min(5, 9) = 5
st[5][1] = min(9, 2) = 2
st[6][1] = min(2, 6) = 2
第三步:计算j=2(长度为4)
st[i][2] = min(st[i][1], st[i+2][1])
st[0][2] = min(1, 1) = 1
st[1][2] = min(1, 1) = 1
st[2][2] = min(1, 5) = 1
st[3][2] = min(1, 2) = 1
st[4][2] = min(5, 2) = 2
第四步:计算j=3(长度为8)
st[i][3] = min(st[i][2], st[i+4][2])
st[0][3] = min(1, 2) = 1
ST表完整形式:
j=0 j=1 j=2 j=3
i=0: 3 1 1 1
i=1: 1 1 1 -
i=2: 4 1 1 -
i=3: 1 1 1 -
i=4: 5 5 2 -
i=5: 9 2 - -
i=6: 2 2 - -
i=7: 6 - - -
// 稀疏表(Sparse Table)用于解决RMQ(区间最值查询)问题
class SparseTable {
private:
vector<vector<int>> st; // st[i][j]表示从i开始长度为2^j的区间的最小值
vector<int> log2; // log2[i]表示log2(i)的向下取整
int n; // 数组长度
public:
SparseTable(const vector<int>& arr) {
n = arr.size();
// 预计算log2数组
log2.resize(n + 1);
log2[1] = 0;
for (int i = 2; i <= n; i++) {
log2[i] = log2[i/2] + 1;
}
// 构建稀疏表
int logN = log2[n] + 1;
st.resize(n, vector<int>(logN));
// 初始化长度为1的区间
for (int i = 0; i < n; i++) {
st[i][0] = arr[i];
}
// 动态规划构建稀疏表
for (int j = 1; j < logN; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[i][j] = min(st[i][j-1], st[i + (1 << (j-1))][j-1]);
}
}
}
// 查询区间[l, r]的最小值,O(1)时间
int queryMin(int l, int r) {
int j = log2[r - l + 1];
return min(st[l][j], st[r - (1 << j) + 1][j]);
}
};
#include <bits/stdc++.h>
using namespace std;
// ST表模板 - 支持任意可结合运算
template<typename T, typename Combine>
class SparseTable {
private:
vector<vector<T>> st;
vector<int> log2;
int n;
Combine combine;
public:
SparseTable(const vector<T>& arr, Combine cmp) : combine(cmp) {
n = arr.size();
// 预计算log2数组
log2.resize(n + 1);
log2[1] = 0;
for (int i = 2; i <= n; i++) {
log2[i] = log2[i / 2] + 1;
}
// 构建稀疏表
int logN = log2[n] + 1;
st.resize(n, vector<T>(logN));
// 初始化长度为1的区间
for (int i = 0; i < n; i++) {
st[i][0] = arr[i];
}
// 动态规划构建
for (int j = 1; j < logN; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[i][j] = combine(st[i][j-1], st[i + (1 << (j-1))][j-1]);
}
}
}
// 查询区间[l, r]
T query(int l, int r) {
int j = log2[r - l + 1];
return combine(st[l][j], st[r - (1 << j) + 1][j]);
}
};
// 使用示例
int main() {
vector<int> arr = {3, 1, 4, 1, 5, 9, 2, 6};
// 求最小值
auto minFunc = [](int a, int b) { return min(a, b); };
SparseTable<int, decltype(minFunc)> stMin(arr, minFunc);
// 求最大值
auto maxFunc = [](int a, int b) { return max(a, b); };
SparseTable<int, decltype(maxFunc)> stMax(arr, maxFunc);
// 查询示例
cout << "区间[2, 5]的最小值: " << stMin.query(2, 5) << endl;
cout << "区间[0, 7]的最大值: " << stMax.query(0, 7) << endl;
return 0;
}
GCD的性质:
ST表不仅适用于最值查询,还适用于任何满足结合律的运算,如GCD。
GCD的结合律:
因此ST表的递推公式同样适用:
实现代码:
// GCD的ST表实现
class GCDSparseTable {
private:
vector<vector<int>> st;
vector<int> log2;
int n;
int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
public:
GCDSparseTable(const vector<int>& arr) {
n = arr.size();
// 预计算log2
log2.resize(n + 1);
log2[1] = 0;
for (int i = 2; i <= n; i++) {
log2[i] = log2[i/2] + 1;
}
// 构建ST表
int logN = log2[n] + 1;
st.resize(n, vector<int>(logN));
for (int i = 0; i < n; i++) {
st[i][0] = arr[i];
}
for (int j = 1; j < logN; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[i][j] = gcd(st[i][j-1], st[i + (1 << (j-1))][j-1]);
}
}
}
int query(int l, int r) {
int j = log2[r - l + 1];
return gcd(st[l][j], st[r - (1 << j) + 1][j]);
}
};
GCD查询示例:
给定数组
构建ST表:
j=0: [12, 18, 24, 30, 36]
j=1: [6, 6, 6, 6] (gcd(12,18)=6, gcd(18,24)=6, ...)
j=2: [6, 6]
查询区间[1, 3]的GCD:
验证:
区间
复杂度:
查询示例1:查询区间[2, 5]的最小值
步骤分析:
区间长度:
计算k值:
分解区间:
查询结果:
验证:
最小值确实是1
查询示例2:查询区间[1, 6]的最小值
区间长度:
计算k值:
分解区间:
查询结果:
可视化查询过程:
数组: 3, 1, 4, 1, 5, 9, 2, 6
索引: 0 1 2 3 4 5 6 7
查询[2, 5]:
[2, 5]长度=4
k=2, 2^k=4
左区间[2,5]: ━━━━━━
右区间[2,5]: ━━━━━━
重叠部分保证覆盖
查询[1, 6]:
[1, 6]长度=6
k=2, 2^k=4
左区间[1,4]: ━━━━━━
右区间[3,6]: ━━━━━━
━━━━━━ (重叠)
两个区间覆盖[1,6]
log2数组预计算:
为了避免每次查询都计算log2,我们预计算:
log2[1] = 0
log2[2] = 1
log2[3] = 1
log2[4] = 2
log2[5] = 2
log2[6] = 2
log2[7] = 2
log2[8] = 3
...
递推公式:
问题描述:
给定一个长度为n的数组arr和多个查询(l, r),每次查询需要返回arr[l...r]中的最小值。
预处理步骤:
查询步骤:
示例:
考虑数组arr = [3, 1, 4, 1, 5, 9, 2, 6]
预处理ST表(部分):
查询区间[2, 5]的最小值:
复杂度分析:
ST表的局限性
- 静态数据:ST表不支持动态修改,一旦构建完成,数组不能改变
- 空间复杂度:需要
的额外空间,对于大规模数据可能有压力 - 仅适用于可重叠查询:有些问题要求不重叠区间,ST表不适用
- 不适合单点更新:如果需要频繁单点更新,应该使用线段树等数据结构
- 不满足结合律的运算:如减法、除法等不满足结合律,无法使用ST表
- 预处理开销:对于查询次数很少的情况,预处理开销可能不值得
ST表优势:
适用场景:
线段树优势:
适用场景:
选择建议:
|| 特性 | ST表 | 线段树 |
||------|------|--------|
|| 查询复杂度 |
|| 更新复杂度 | 不支持 |
|| 空间复杂度 |
|| 适用性 | 静态查询 | 动态更新 |
[CSES 1649] Minimum Queries I
[CSES 1650] Maximum Queries I
[CSES 1143] Hotel Queries
[CSES 1651] Range Update Queries
[Codeforces 1516D] Cut
[Codeforces 1314D] Minimum Path
ST表的核心要点:
基本思想:
关键技巧:
常见应用:
实践建议:
编码前:
编码时:
调试时:
ST表的扩展:
持久化ST表:
动态ST表:
ST表与其他算法结合:
高级应用:
实际应用中的注意事项:
在使用ST表之前,先考虑:
优化建议:
记住:

演讲者备注不会在演示模式中显示给观众,仅供演讲者参考。 每个关键页面都添加了备注,帮助演讲者更好地讲解内容。
封面页: - 欢迎学生,简要介绍本次课程的主题和目标 - 强调ST表是倍增法的重要应用,在区间查询问题中非常关键 - 可以简单提及本课程将重点讲解ST表的原理、实现和应用
目录页: - 简要介绍本次讲座的结构和内容安排 - 可以提及讲座的逻辑流程:从RMQ问题到ST表原理,再到详细推导和应用 - 强调我们将通过多个例子来深入理解ST表的应用 - 提醒学生关注每个部分之间的联系,以及如何将理论应用到实践中
转场页: - 这是一个简单的转场页,用于引入RMQ问题概述部分 - 可以简单提及RMQ问题是区间查询中的经典问题
转场页: - 引入ST表原理与推导部分 - 可以简单提及理解原理有助于更好地实现ST表
转场页: - 引入ST表代码实现部分 - 可以简单提及掌握这些代码模板有助于快速实现ST表
转场页: - 引入ST表应用实例部分 - 可以简单提及我们将通过几个经典例子来深入理解ST表的应用