静态区间最值查询:ST 表倍增解法精讲

信奥新赛季备考 · 提高组数据结构高频考点。本文用一道「校园气象观测」原创题,带你吃透倍增思想静态区间最值查询(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)
  • 空间stn × (log n + 1) 的二维数组 → O(n log n)

横向对比:

结构建 / 预处理单次查询能否修改
ST 表O(n log n)O(1)❌ 静态
线段树O(n)O(log n)✅ 支持
单调队列(定长滑窗)O(n)O(1)❌ 仅定长

结论:数据不变、查询极多 → 首选 ST 表;区间会改 → 换线段树。

六、高频易错点(考场扣分重灾区)

  1. log 用 log() 浮点函数log(8)/log(2) 可能因精度得到 2.9999999 而取整成 2。务必用递推数组或整数 bit_length()
  2. 两段区间「长度不够」的误解2^k 往往小于区间真实长度,但只要两段并集覆盖 [l,r] 即可(允许重叠),不必恰好相等。
  3. 建表循环顺序写反:必须先枚举 j(长度)再枚举 i,否则长区间会用到还没算好的短区间。
  4. 下标越界:建表内层循环要带 i + (1 << j) - 1 <= n 的边界,否则访问越界;查询时 r - 2^k + 1 也需 ≥ 1。
  5. max / min 方向搞反:求最小值时要把所有 max 改成 min;若同时要最大和最小,应建两张表(或一个 st 存 pair)。
  6. 下标从 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 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青少儿编程课堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值