ST表好难!!!!!(而且也没那么有用)

倍增思想与 ST 表(稀疏表)

一、倍增思想

1. 核心概念

倍增(Binary Lifting)也叫二进制拆分法:将较大的步数/长度拆成若干个 2 的整数次幂之和,每次跳跃 2^k 步,大幅减少操作次数。

任意正整数可唯一表示为 2^0 + 2^1 + 2^2 + ... 的形式,例如:

5 = 2^2 + 2^0
7 = 2^2 + 2^1 + 2^0

2. 为什么要用倍增?

  • 普通遍历:一步一跳,复杂度 O(n)
  • 倍增法:按 2^k 大步跳跃,复杂度 O(log n)

倍增可用于:快速幂、树上最近公共祖先(LCA)、二分优化、ST 表等。

3. 通用流程

  1. 预处理:计算出所有 2^0, 2^1, 2^2, ..., 2^(log n) 层级的数据。
  2. 从大到小尝试:从最大的幂次开始,能跳就跳,不能跳就减小幂次。
  3. 完成目标:直到覆盖整个区间或达到目标状态。

二、ST 表(Sparse Table)

1. 适用场景

ST 表用于静态数组的区间最值查询(RMQ)

  • 静态:数组不发生修改(不支持单点/区间更新)
  • 查询:多次询问区间 [l, r] 的最大值或最小值
  • 复杂度:预处理 O(n log n),单次查询 O(1)

对比:暴力 O(n),线段树 O(log n),ST 表查询最快。

2. 数组定义

设原数组为 a[1..n](下标从 1 开始)。

定义二维数组 st[k][i] 表示:从位置 i 开始,长度为 2^k 的区间的最值。

  • k = 0:长度 1,st[0][i] = a[i]
  • k = 1:长度 2,st[1][i] = max(a[i], a[i+1])
  • k = 2:长度 4,st[2][i] = max(a[i..i+3])

3. 预处理(动态规划 + 倍增)

(1) 初始条件 (k=0)

st[0][i] = a[i],其中 1 ≤ i ≤ n

(2) 递推公式 (k ≥ 1)

长度为 2^k 的区间可以拆成两个长度为 2^(k-1) 的子区间:

  • 左半:起点 i,长度 2^(k-1) → st[k-1][i]
  • 右半:起点 i+2^(k-1),长度 2^(k-1) → st[k-1][i+2^(k-1)]

取最大值:st[k][i] = max( st[k-1][i], st[k-1][i+2^(k-1)] )

边界条件:i + 2^k - 1 ≤ n

(3) 预处理 log 数组

为了快速得到 k = floor(log2(len)),提前计算 log2 数组:

4. 区间查询

查询 [l, r] 的最值:

  1. 区间长度 len = r - l + 1
  2. 取 k = log2[len]
  3. 用两个长度为 2^k 的区间覆盖 [l, r]:
    • 第一个区间:[l, l+2^k-1] → st[k][l]
    • 第二个区间:[r-2^k+1, r] → st[k][r-2^k+1]
  4. 结果:ans = max( st[k][l], st[k][r-2^k+1] )

两个区间允许重叠,因为最值运算满足可重复贡献性质。

示例

数组 a = [1,3,5,2,4],查询 [2,4](元素 3,5,2):

  • len = 3,k = floor(log2 3) = 1,2^k = 2
  • 第一段:st[1][2] → 区间 [2,3] 的最大值 5
  • 第二段:st[1][3] → 区间 [3,4] 的最大值 5
  • 答案 = max(5,5) = 5

三、完整代码(C++)

版本1:区间最大值

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int MAXN = 100010;
const int LOG = 20;   // 2^20 > 1e5

int st[LOG][MAXN];
int log2n[MAXN];
vector<int> a;

void init(int n) {
    // 预处理 log2
    log2n[1] = 0;
    for (int i = 2; i <= n; ++i)
        log2n[i] = log2n[i / 2] + 1;

    // 初始化 k=0
    for (int i = 1; i <= n; ++i)
        st[0][i] = a[i];

    // 递推
    int max_k = log2n[n] + 1;
    for (int k = 1; k <= max_k; ++k) {
        for (int i = 1; i + (1 << k) - 1 <= n; ++i) {
            int mid = i + (1 << (k - 1));
            st[k][i] = max(st[k-1][i], st[k-1][mid]);
        }
    }
}

int query(int l, int r) {
    int k = log2n[r - l + 1];
    return max(st[k][l], st[k][r - (1 << k) + 1]);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    a.resize(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> a[i];

    init(n);

    while (m--) {
        int l, r;
        cin >> l >> r;
        cout << query(l, r) << '\n';
    }
    return 0;
}
posted @ 2026-06-13 16:17  十七code  阅读(8)  评论(0)    收藏  举报