用倍增递推建 ST 表预处理所有 2 的幂长度区间的最大值,每次询问取两块可重叠的 2 的幂区间求 max,O(1) 回答区间最大值。
OJ: roj
题目 ID: 1544
难度:普及+/提高-
标签:ST表RMQ倍增python
创建: 2026-09-30 17:18
更新: 2026-10-07 11:01
形式化题目
给定长度为
所有询问期间数列不发生修改,即静态区间最大值查询(RMQ)。输入样例数列为
正解
思路
朴素做法与瓶颈。 每次询问从
关键观察:
倍增建表。 长度
其中
| 0(长 1) | 34 | 1 | 8 | 123 | 3 | 2 |
| 1(长 2) | 34 | 8 | 123 | 123 | 3 | |
| 2(长 4) | 123 | 123 | 123 |
观察点:第 max 得到,
例如
查询两块覆盖。 对询问 max:
因为
查对数与输出。 len.bit_length() - 1 直接得到,
整数运算,无浮点误差;本题 logs 表,
输出先攒进列表最后一次性写出。
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 17:21
# update_at: 2026-09-30 17:21
import sys
from collections.abc import Iterator
def build_st(a: list[int]) -> list[list[int]]:
"""ST 表:st[k][i] = a[i..i+2^k-1] 的最大值,由下一层两个半段合并。"""
st = [a]
span = 2 # 当前层区间长度
while span <= len(a):
prev = st[-1]
half = span >> 1
st.append([max(prev[i], prev[i + half]) for i in range(len(a) - span + 1)])
span <<= 1
return st
def query(st: list[list[int]], left: int, right: int) -> int:
"""回答闭区间 [left, right] 的最大值:两块可重叠的 2 的幂区间取 max。"""
length = right - left + 1
k = length.bit_length() - 1 # 不超过区间长度的最大 2 的幂指数
# 幂等的 max 允许重叠,两块并起来恰好盖住 [left, right]
return max(st[k][left], st[k][right - (1 << k) + 1])
def solve() -> None:
data: Iterator[int] = iter(map(int, sys.stdin.buffer.read().split()))
count = next(data) # 数字个数 N
values = [next(data) for _ in range(count)]
ask_count = next(data) # 询问次数 M
st = build_st(values)
# 题目按 1-based 给出左右端点,依次减 1 转成 0-based 下标
out = [str(query(st, next(data) - 1, next(data) - 1)) for _ in range(ask_count)]
sys.stdout.write('\n'.join(out))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 07:19
* update_at: 2026-10-05 07:19
*/
// main.cpp:静态区间最大值(RMQ),ST 表(倍增)正解。
// 预处理 O(N log N),每次询问 O(1),满足 N <= 2e5、M <= 1e4 的规模。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
const int LOG = 18; // 2^18 > 2e5,层数够用
typedef long long ll;
int n, m;
ll a[MAXN]; // a[i]:第 i 个数(下标从 1 开始,与题面对应)
ll st[LOG][MAXN]; // st[k][i]:从 i 开始、长度为 2^k 的区间的最大值
// 预处理 ST 表:长度 2^k 的区间由两段长度 2^(k-1) 的区间拼成。
// max 有幂等性(重叠不影响结果),所以查询时允许两块区间重叠。
void build_st() {
for (int i = 1; i <= n; i++) st[0][i] = a[i];
for (int k = 1; (1 << k) <= n; k++) {
for (int i = 1; i + (1 << k) - 1 <= n; i++) {
st[k][i] = max(st[k - 1][i], st[k - 1][i + (1 << (k - 1))]);
}
}
}
// 回答询问 [l, r] 的最大值:取两块长度 2^k 的区间覆盖 [l, r]。
// k = floor(log2(len)),两块起点分别是 l 和 r - 2^k + 1。
ll query_max(int l, int r) {
int k = 0;
while ((1 << (k + 1)) <= r - l + 1) k++; // 求 floor(log2(len)),不用浮点
return max(st[k][l], st[k][r - (1 << k) + 1]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> m;
build_st();
for (int i = 1; i <= m; i++) {
int A, B;
cin >> A >> B;
cout << query_max(A, B) << "\n";
}
return 0;
}复杂度
- 时间:建表
,查询每条 ,合计 。 - 空间:
个表项, 时约 个整数; 本题 Python 实现实测峰值约 67.7 MB,在 128 MB 限制内。
总结
静态 RMQ 的标准解法是 ST 表: