RMQ (区间最值查询) 模板详解

RMQ (区间最值查询) 模板详解

1. 简介

RMQ(Range Minimum Query)是一种用于解决区间最值查询问题的数据结构。它能够在 O(1) 的时间复杂度内查询任意区间的最小值(或最大值)。本模板采用了 ST 表(Sparse Table)和分块优化的混合策略,实现了更高效的查询。

主要特点:

  • 支持任意类型的比较操作
  • 预处理时间复杂度 O(n log n)
  • 查询时间复杂度 O(1)
  • 空间复杂度 O(n log n)

2. 实现原理

2.1 基本结构

该实现结合了多种优化技术:

  1. ST 表用于处理大区间查询
  2. 分块处理用于优化小区间查询
  3. 预处理块内前缀最小值和后缀最小值
  4. 使用位运算优化块内查询

2.2 核心策略

  1. 分块处理

    • 将数组分成固定大小(64)的块
    • 对每个块预处理内部信息
  2. ST 表优化

    • 用于处理跨越多个块的查询
    • 保证 O(1) 的查询时间复杂度
  3. 前缀/后缀数组

    • pre[] 存储每个位置到所在块开始的最小值
    • suf[] 存储每个位置到所在块结束的最小值
  4. 位运算优化

    • 使用 64 位整数存储块内状态
    • 通过位运算快速查询块内最小值

3. 模板代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
template<class T, class Cmp = std::less<T>>
struct RMQ {
const Cmp cmp = Cmp();
static constexpr unsigned B = 64;
using u64 = unsigned long long;
int n;
std::vector<std::vector<T>> a;
std::vector<T> pre, suf, ini;
std::vector<u64> stk;

RMQ() {}
RMQ(const std::vector<T> &v) {
init(v);
}

void init(const std::vector<T> &v) {
n = v.size();
pre = suf = ini = v;
stk.resize(n);
if (!n) {
return;
}

// 分块处理
const int M = (n - 1) / B + 1;
const int lg = std::__lg(M);
a.assign(lg + 1, std::vector<T>(M));

// 预处理每块的最小值
for (int i = 0; i < M; i++) {
a[0][i] = v[i * B];
for (int j = 1; j < B && i * B + j < n; j++) {
a[0][i] = std::min(a[0][i], v[i * B + j], cmp);
}
}

// 预处理前缀最小值
for (int i = 1; i < n; i++) {
if (i % B) {
pre[i] = std::min(pre[i], pre[i - 1], cmp);
}
}

// 预处理后缀最小值
for (int i = n - 2; i >= 0; i--) {
if (i % B != B - 1) {
suf[i] = std::min(suf[i], suf[i + 1], cmp);
}
}

// 构建 ST 表
for (int j = 0; j < lg; j++) {
for (int i = 0; i + (2 << j) <= M; i++) {
a[j + 1][i] = std::min(a[j][i], a[j][i + (1 << j)], cmp);
}
}

// 预处理块内信息
for (int i = 0; i < M; i++) {
const int l = i * B;
const int r = std::min(1U * n, l + B);
u64 s = 0;
for (int j = l; j < r; j++) {
while (s && cmp(v[j], v[std::__lg(s) + l])) {
s ^= 1ULL << std::__lg(s);
}
s |= 1ULL << (j - l);
stk[j] = s;
}
}
}

T operator()(int l, int r) {
if (l / B != (r - 1) / B) {
// 跨块查询
T ans = std::min(suf[l], pre[r - 1], cmp);
l = l / B + 1;
r = r / B;
if (l < r) {
int k = std::__lg(r - l);
ans = std::min({ans, a[k][l], a[k][r - (1 << k)]}, cmp);
}
return ans;
} else {
// 同块查询
int x = B * (l / B);
return ini[__builtin_ctzll(stk[r - 1] >> (l - x)) + l];
}
}
};

4. 函数说明

4.1 构造函数和初始化

1
2
3
RMQ() {}
RMQ(const std::vector<T> &v)
void init(const std::vector<T> &v)
  • 支持默认构造和基于数组的构造
  • init 函数完成所有预处理工作
  • 支持自定义比较函数

4.2 查询函数

1
T operator()(int l, int r)
  • 查询区间 [l, r) 内的最小值
  • 根据查询区间是否跨块采用不同策略
  • 返回区间最小值

5. 时间复杂度分析

  • 初始化:O(n log n)

    • ST 表构建:O(n log n)
    • 分块预处理:O(n)
    • 前缀/后缀数组:O(n)
  • 查询:O(1)

    • 同块查询:O(1),使用位运算
    • 跨块查询:O(1),使用 ST 表
  • 空间复杂度:O(n log n)

    • ST 表:O(n log n)
    • 其他数组:O(n)

6. 应用场景

  1. 区间最值查询问题
  2. 可视化最近点对问题
  3. 最长公共前缀(LCP)问题
  4. 区间 GCD 查询
  5. 各种需要快速查询区间统计信息的场景

7. 注意事项

  1. 模板支持自定义比较函数,可以查询最大值或其他统计量
  2. 查询区间为左闭右开 [l, r)
  3. 分块大小设为 64 是为了优化位运算操作
  4. 初始化时需要足够的内存空间

8. 总结

RMQ 是一个强大的数据结构,本模板通过结合 ST 表和分块思想,实现了高效的区间查询。它的实现虽然较为复杂,但查询效率极高,是解决区间最值问题的理想选择。