信奥新赛季备考 · 提高组数据结构高频考点。本文用一道「校园气象观测」原创题,带你吃透倍增思想与静态区间最值查询(RMQ),给出 C++ / Python 双版可运行代码、考点拆解、复杂度分析与高频易错点,并延伸到进阶用法。
一、引言:为什么必须会 ST 表
在信息学竞赛里,「给你一个不变的数组,反复询问某段区间里的最大值 / 最小值」是一类经典问题,学名 RMQ(Range Minimum/Maximum Query,区间最值查询)。
提高组大纲把 ST 表明确列为「高阶线性结构」的必会内容。它的魅力在于:预处理一次,之后每次查询只要 O(1),而线段树每次还要 O(log n)。只要题目是静态数据(区间不再修改),ST 表几乎总是更优、也更好写的选择。
二、题目 / 项目目标
【校园气象观测站】
气象站连续记录了 n 天(1 ≤ n ≤ 10^5)的每日最高气温 a[1…n]。接下来有 q 次查询(1 ≤ q ≤ 10^5),每次给定区间 [l, r](1 ≤ l ≤ r ≤ n),请你输出这段时间内的最高气温。
要求:单次查询尽量快(理想 O(1)),总数据规模达到十万级也不能超时。
样例输入
n = 10, q = 5
气温:3 1 4 1 5 9 2 6 5 3
查询:
[1, 10] → 9
[2, 5] → 5
[3, 7] → 9
[6, 6] → 9
[1, 1] → 3

三、核心考点拆解
| 考点 | 说明 |
|---|---|
| 倍增思想 | 用 2^j 长度把区间「跳着」合并,是 ST 表、LCA、快速幂的共同内核 |
| 稀疏表(Sparse Table) | st[i][j] 表示「从 i 开始、长度为 2^j 的区间」的最值 |
| 区间可重复覆盖 | 查询时把 [l,r] 拆成两段长度为 2^k 的区间(允许重叠),并集恰好盖满 |
| O(n log n) 预处理 / O(1) 查询 | 空间换时间,以 log n 层状态换取常数级询问 |
| log 预处理技巧 | 用递推避免 log() 浮点误差,是考场稳定得分的关键 |
四、解法:倍增建表 + 两区间拼答案
4.1 状态定义与建表
令 st[i][j] = max{ a[i], a[i+1], …, a[i + 2^j - 1] },即从 i 起、长度 2^j 的连续段的最大值。
- 初值:
st[i][0] = a[i](长度2^0 = 1的区间就是它自己)。 - 转移:把长度为
2^j的区间从中间劈成两半,每段长2^(j-1),于是
st[i][j] = max( st[i][j-1], st[i + 2^(j-1)][j-1] )。
注意外层循环是 j(长度),必须先算短区间、再拼长区间。
4.2 查询
对询问 [l, r],令 len = r - l + 1,取 k = ⌊log2(len)⌋(即最大的 2^k ≤ len)。用两段长度为 2^k 的区间盖住 [l, r]:
左段:st[l][k] 覆盖 [l, l + 2^k - 1]
右段:st[r - 2^k + 1][k] 覆盖 [r - 2^k + 1, r]
两段并集正好覆盖 [l, r],且允许重叠(取 max 不受重叠影响),所以:
ans = max( st[l][k], st[r - 2^k + 1][k] )
4.3 C++ 实现(考场版)
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
const int K = 17; // 2^17 > 1e5,足够覆盖
int a[MAXN];
int st[MAXN][K + 1];
int lg[MAXN]; // lg[i] = floor(log2(i)),递推求
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
// 递推 log2,杜绝浮点误差
lg[1] = 0;
for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
// 倍增建表
for (int i = 1; i <= n; i++) st[i][0] = a[i];
for (int j = 1; j <= K; j++) {
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]);
}
}
while (q--) {
int l, r;
cin >> l >> r;
int len = r - l + 1;
int k = lg[len];
int ans = max(st[l][k], st[r - (1 << k) + 1][k]);
cout << ans << "\n";
}
return 0;
}
4.4 Python 实现(自测 / 理解版)
import sys
def build_st(a, n):
K = n.bit_length() # 2^K >= n
st = [[0] * (K + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
st[i][0] = a[i]
for j in range(1, K + 1):
step = 1 << (j - 1)
# i 的范围保证 i + 2^j - 1 <= n
for i in range(1, n - (1 << j) + 2):
st[i][j] = max(st[i][j - 1], st[i + step][j - 1])
return st
def query(st, l, r):
length = r - l + 1
k = length.bit_length() - 1 # 等价于 floor(log2(length))
step = 1 << k
return max(st[l][k], st[r - step + 1][k])
def main():
data = sys.stdin.read().split()
if not data:
return
it = iter(data)
n = int(next(it)); q = int(next(it))
a = [0] + [int(next(it)) for _ in range(n)]
st = build_st(a, n)
out = []
for _ in range(q):
l = int(next(it)); r = int(next(it))
out.append(str(query(st, l, r)))
sys.stdout.write("\n".join(out))
if __name__ == "__main__":
main()
上述两份代码已对拍验证:样例输出
9 / 5 / 9 / 9 / 3与暴力一致;随机 8000 组含负数测试全部吻合。
五、时间 / 空间复杂度
- 建表:
i循环 O(n),j循环 O(log n) → O(n log n)。 - 单次查询:只做两次查表与一次比较 → O(1)。
- 空间:
st为n × (log n + 1)的二维数组 → O(n log n)。
横向对比:
| 结构 | 建 / 预处理 | 单次查询 | 能否修改 |
|---|---|---|---|
| ST 表 | O(n log n) | O(1) | ❌ 静态 |
| 线段树 | O(n) | O(log n) | ✅ 支持 |
| 单调队列(定长滑窗) | O(n) | O(1) | ❌ 仅定长 |
结论:数据不变、查询极多 → 首选 ST 表;区间会改 → 换线段树。
六、高频易错点(考场扣分重灾区)
- log 用
log()浮点函数:log(8)/log(2)可能因精度得到2.9999999而取整成 2。务必用递推数组或整数bit_length()。 - 两段区间「长度不够」的误解:
2^k往往小于区间真实长度,但只要两段并集覆盖[l,r]即可(允许重叠),不必恰好相等。 - 建表循环顺序写反:必须先枚举
j(长度)再枚举i,否则长区间会用到还没算好的短区间。 - 下标越界:建表内层循环要带
i + (1 << j) - 1 <= n的边界,否则访问越界;查询时r - 2^k + 1也需 ≥ 1。 - max / min 方向搞反:求最小值时要把所有
max改成min;若同时要最大和最小,应建两张表(或一个st存 pair)。 - 下标从 0 还是 1 开始不统一:本文统一 1-based,若用 0-based 需同步调整
lg、循环与查询偏移。
七、进阶玩法
7.1 静态区间 GCD
最大公约数满足 gcd(a,b,c) = gcd(gcd(a,b), c),同样具有「区间可合并」性质,把 max 换成 gcd 即可做静态区间 GCD。
7.2 二维 ST 表
矩阵中求某个子矩形的最值:先对每一行做一维 ST 表,再对「列方向」做一维 ST 表,两层倍增即可 O(1) 查询子矩形最值。
7.3 RMQ 与 LCA 互转
任意数组可构造笛卡尔树,其欧拉序列上的 RMQ 等价于树上两点的 LCA——这把「区间最值」和「最近公共祖先」两座看似无关的桥连了起来,是省选常用技巧。
7.4 带修改怎么办
ST 表天然只支持静态。一旦区间要单点 / 区间修改,立刻退回到线段树或树状数组;若修改极少、查询极多,也可考虑「分块」折中。
八、小结与互动
ST 表是「倍增思想」最干净的一次落地:预处理 O(n log n)、查询 O(1)、代码极短。记住三件事就能稳拿分——外层循环是 j、查询用两段重叠区间覆盖、log 用递推别用浮点。
💡 练手题:把上面的代码改成「区间最小值」版本(同时输出最大和最小),再试着用 ST 表做「静态区间 GCD」。欢迎在评论区贴出你的实现,或聊聊你在比赛里被 RMQ 坑过的瞬间~
如果觉得这篇精讲有帮助,点个赞 / 收藏,备考路上我们下一篇见!
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:
- 全国青少年信息素养大赛复赛集训题目Python&C++.docx
https://pan.quark.cn/s/93995d3cb150 - 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
https://pan.quark.cn/s/da97b5dbf75d - Python背记手册.pdf
https://pan.quark.cn/s/7568ae9ca92b - Python课程
https://pan.quark.cn/s/a94bf02d00c6 - 2024信息素养大赛图形化复赛集训题答案3-9
https://pan.quark.cn/s/6ccab7ec3cbc - 2025年03月份电子学会考级真题
https://pan.quark.cn/s/4403c4228912 - 2025全国青少年信息素养大赛赛项说明
https://pan.quark.cn/s/d9d0df4a9f29 - 青少儿信息素养大赛编程资料
https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注获取最新分享。
859

被折叠的 条评论
为什么被折叠?



