对最大、最小各建一张 ST 表,询问用两段可重叠的 2 幂区间 O(1) 取出极值,极差即两表相减。
OJ: roj
题目 ID: 1545
难度:普及+/提高-
标签:ST表RMQ倍增python
创建: 2026-09-30 17:18
更新: 2026-10-07 12:15
形式化题目
给定长度为
询问期间数列不发生修改,即静态区间极差查询(RMQ 的极差形式)。
样例数列
正解
思路
朴素做法与瓶颈。 每次询问从
关键观察:
倍增建表。 长度
其中
| 层(长度) | 运算 | ||||||
|---|---|---|---|---|---|---|---|
| 0(长 1) | max | 1 | 7 | 3 | 4 | 2 | 5 |
| 1(长 2) | max | 7 | 7 | 4 | 4 | 5 | |
| 2(长 4) | max | 7 | 7 | 5 | |||
| 0(长 1) | min | 1 | 7 | 3 | 4 | 2 | 5 |
| 1(长 2) | min | 1 | 3 | 3 | 2 | 2 | |
| 2(长 4) | min | 1 | 2 | 2 |
观察点:每层由上一层相隔 pick 得到,例如
查询两段覆盖。 对询问
因为
查对数与查表。 length.bit_length() - 1 直接得到,
不做浮点运算;询问较多时输出先攒进列表最后一次性写出。
代码
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:19
# update_at: 2026-09-30 17:19
import sys
from collections.abc import Callable
def build(heights: list[int], pick: Callable[[int, int], int]) -> list[list[int]]:
"""稀疏表:st[j][i] = 区间 [i, i+2^j-1] 的极值,pick 决定取最大还是最小。"""
size = len(heights)
st = [heights]
j = 1
while (1 << j) <= size:
prev = st[-1]
# 两个半长 2^(j-1) 的相邻区间合并成长度 2^j 的区间
st.append([pick(prev[i], prev[i + (1 << (j - 1))]) for i in range(size - (1 << j) + 1)])
j += 1
return st
def range_gap(st_max: list[list[int]], st_min: list[list[int]], left: int, right: int) -> int:
"""闭区间 [left, right] 内最高与最低身高之差。"""
length = right - left + 1
j = length.bit_length() - 1 # 2^j <= length < 2^(j+1)
tail = right - (1 << j) + 1 # 两段 2^j 覆盖整个区间,允许重叠
return max(st_max[j][left], st_max[j][tail]) - min(st_min[j][left], st_min[j][tail])
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n, q = next(data), next(data)
heights = [next(data) for _ in range(n)] # 每行一头牛的身高
st_max = build(heights, max)
st_min = build(heights, min)
out: list[str] = []
for _ in range(q):
left, right = next(data) - 1, next(data) - 1 # 题目 1-indexed → 0-indexed
out.append(str(range_gap(st_max, st_min, left, right)))
print('\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:27
* update_at: 2026-10-05 07:27
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005; // 牛的数量上限 5*10^4
const int MAXLOG = 17; // 2^16 <= 5*10^4 < 2^17,所以只需 0..16 层
typedef long long ll;
ll n, q;
// 身高最大 10^6,区间极值用 int 存即可,省一半内存
int st_max[MAXLOG][MAXN]; // st_max[k][i]:区间 [i, i+2^k-1] 内的最高身高
int st_min[MAXLOG][MAXN]; // st_min[k][i]:区间 [i, i+2^k-1] 内的最低身高
int lg[MAXN]; // lg[len] = floor(log2(len))
// 倍增建表:长度 2^k 的区间由两个长度 2^(k-1) 的区间拼成。
void build_st() {
lg[1] = 0;
for (ll i = 2; i <= n; i++) {
lg[i] = lg[i / 2] + 1;
}
for (ll k = 1; (1LL << k) <= n; k++) {
ll half = 1LL << (k - 1);
ll cnt = n - (1LL << k) + 1; // 该层能作为起点的个数
for (ll i = 1; i <= cnt; i++) {
st_max[k][i] = max(st_max[k - 1][i], st_max[k - 1][i + half]);
st_min[k][i] = min(st_min[k - 1][i], st_min[k - 1][i + half]);
}
}
}
void solve() {
cin >> n >> q;
for (ll i = 1; i <= n; i++) {
cin >> st_max[0][i];
st_min[0][i] = st_max[0][i];
}
build_st();
for (ll t = 1; t <= q; t++) {
ll a, b;
cin >> a >> b;
ll len = b - a + 1;
ll k = lg[len];
// 两段长度为 2^k 的可重叠区间恰好盖住 [a, b]
ll tail = b - (1LL << k) + 1;
int hi = max(st_max[k][a], st_max[k][tail]);
int lo = min(st_min[k][a], st_min[k][tail]);
cout << hi - lo << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}复杂度
- 时间:建两张表各
,每条询问 ,合计 , 与 C++ 正解(ST 表)同阶;对比朴素 。 - 空间:两张表共
个表项, 时约 个整数; Python 实现实测峰值 59.6 MB,官方 9 组数据全部正确(本地 0.018–0.160 s,题面限时 1000 ms)。
总结
静态区间极差 = 两次静态 RMQ。ST 表利用