【数据结构】ST表 学习笔记

ST表

RMQ 问题

RMQ 是英文 Range Maximum/Minimum Query 的缩写,表示区间最大(最小)值。
ST 表是用于解决离线 RMQ 问题的一种线性数据结构,在全国青少年信息学奥林匹克系列竞赛大纲中难度为 6,是提高级中学习的数据结构。

倍增思想

考虑每个长度为 2 的正整数幂的区间,这个区间的最值可以分为左右两个长度相等的区间,同时这两个区间的长度又是 2 的整数幂。
所以,我们可以从小到大来递推处理出所有长度为 2 的正整数幂的区间的最值。
形式化地,以最大值为例,记 \(st[i,j]\) 表示以 \(i\) 为左端点,长度是 \(2^j\) 的区间(区间\([i,i+2^j-1]\))的最大值,分为左右两个长度相等的区间,则有

\[st[i,j]=\max(st[i,j-1],st[i+2^{j-1},j-1]) \]

边界条件是 \(st[i,0]=a[i]\),据此递推即可。(\(2^0=1\)
时间复杂度为 \(O(n \log n)\)
通过两个长度为 2 的整数幂的区间,可以求出任意一个区间的最值(如下图,图来自 OI Wiki)
img
形式化地,有

\[\max (A[l...r])=max(st[l,k],st[r-2^k+1,k]) \]

其中,\(k\) 是满足 \(2^k \le r-l+1\) 的最大整数,这样可以保证两个长度是 2 的整数幂的区间把整个区间覆盖。

代码实现

洛谷 P3865 【模板】ST 表

#include <iostream>
#include <cmath>

using namespace std;

#define pow2(x) (1<<(x))

const int N = 1e5 + 5, M = 40;

int n, m, a[N], st[N][M];

inline int read() {
    int x = 0, f = 1;char ch = getchar();
    while (ch < '0' || ch>'9') { if (ch == '-') f = -1;ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48;ch = getchar(); }
    return x * f;
}

// 1.ST 表的初始化(预处理每个长度是 2 的整数幂的区间最值)
void ST_init() {
    for (int i = 1;i <= n;i++) st[i][0] = a[i];
    int t = log(n) / log(2);
    for (int j = 1;j <= t;j++) {
        for (int i = 1;i + pow2(j) - 1 <= n;i++)
            st[i][j] = max(st[i][j - 1], st[i + pow2(j - 1)][j - 1]);
    }
}

// 2.ST 表的查询
int ST_query(int l, int r) {
    int len = r - l + 1;
    int k = log(len) / log(2);
    return max(
        st[l][k],
        st[r - pow2(k) + 1][k]
    );
}

int main() {
    #ifndef ONLINE_JUDGE
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
    #endif
    n = read();
    m = read();
    for (int i = 1;i <= n;i++) a[i] = read();
    ST_init();
    for (int i = 1;i <= m;i++) {
        // 3. 本题常数要求较高,需要进行常数优化。
        int l, r;
        l = read();
        r = read();
        // cout << ST_query(l, r) << endl;
        printf("%d\n", ST_query(l, r));
    }
    return 0;
}

例题

【CSP-S 2022】策略游戏

简要题意:给定数组 \(A\)\(B\),每轮游戏,小 L 会先在 \(A[l_1...r_1]\) 中选一个数,然后小 Q 会在 \(B[l_2...r_2]\) 中选一个数,小 L 让这两个数的乘积尽可能大,而小 Q 让这两个数的乘积尽可能小。求每轮游戏中两人按最优策略选出的乘积的值。

首先小 L 先选数,小 L 在以下几种情况中会选出对应的几种数:

  • \(B[l_2...r_2]\) 中只含负数,则小 L 会选一个最小的负数。
  • \(B[l_2...r_2]\) 中只含正数,则小 L 会选一个最大的正数。
  • \(B[l_2...r_2]\) 中只含 0,显然答案为 0.
  • \(B[l_2...r_2]\) 中即含正数又含负数:
    • 小 L 选择除 \(0\) 外的数答案都为负数。
    • 若小 L 无法选择 \(0\),则会选择一个最大的负数或者一个最小的正数。

接下来考虑小 L 选完后小 Q 的选法:

  • 若小 L 选择一个负数:
    • 若小 Q 可以选择正数,则一定会选择一个最大的正数。
    • 若小 Q 不能选择正数,则会选择一个最大的非正数。
  • 若小 L 选择一个正数:
    • 若小 Q 可以选择负数,则会选择一个最小的负数。
    • 否则,会选一个最小的非负数。

所以,我们用 ST 表维护数组 \(A\) 的区间最小值,区间最大值,区间最小负数,区间最大负数,区间最小正数,区间最大正数,区间是否有 0。再维护 \(B\) 的区间最大值,区间最小值,区间最大正数,区间最大非正数,区间最小负数,区间最小非负数。

则判断一个区间是否含负数(正数),等价于这个这个区间的最小值(最大值)是否是非负数(非正数)。至此这题被解决。

code: 这里

posted @ 2023-01-06 08:46  蒟蒻OIer-zaochen  阅读(154)  评论(0编辑  收藏  举报