ST 表

ST 表使用倍增思想,在 \(O(n \log n)\) 时间构造一个二维表之后,可以在 \(O(1)\) 时间查询区间 \([l, r]\) 的最值。

原理

设原数列为 \(a\)\(f_{i, j}\) 表示区间 \([i, i + 2 ^ {j} - 1]\) ,即左端点为 \(i\),长度为 \(2 ^ j\) 的区间中的最大值。

易知 \(f_{i, 0} = a_i\)

\(j \ne 0\),则该区间可以分成左右两个长度为 \(2 ^ {j - 1}\) 的子区间,答案即为两个子区间最大值中更大的。具体的,我们有:

\[f_{i, j} = \max\{f_{i, j - 1}, f_{i + 2 ^ {j - 1}, j - 1} \} \]

预处理 \(f\) 后,对于给定的区间 \([l, r]\),设 \(k = \lfloor \log_2 (r - l + 1) \rfloor\)。类似地,将查询区间分成左右两个长度为 \(2 ^ k\) 的子区间,取两个区间的最值即可。两个子区间分别从 \(l\) 向后的 \(2 ^ k\) 个数及从 \(r\) 向前的 \(2 ^ k\) 个数。两个子区间可能有重合,但对最值没有影响。

实现

设原数列长度为 \(n\)

创建

考虑 \(i, j\) 的取值范围。

易知 \(j \le \lfloor \log_2 n \rfloor\),且 \(i + 2 ^ {j} - 1 \le n\)

void ST_creat(int n)
{
    int k = std::log2(n);
    for (int i = 1; i <= n; i++)
        f[i][0] = a[i];
    for (int j = 1; j <= k; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
            f[i][j] = std::max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}

查询

左右两个长度为 \(2 ^ k\) 的区间分别为 \([l, l + 2 ^ k - 1]\)\([r - 2 ^ k + 1, r]\)

int ST_query(int l, int r)
{
    int k = std::log2(r - l + 1);
    return std::max(f[l][k], f[r - (1 << k) + 1][k]);
}

完整代码

以下是 P3865 【模板】ST 表 - 洛谷 完整代码。

#include <cmath>
#include <iostream>
#include <algorithm>

int a[100005], f[100005][20];

void ST_creat(int n)
{
    int k = std::log2(n);
    for (int i = 1; i <= n; i++)
        f[i][0] = a[i];
    for (int j = 1; j <= k; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
            f[i][j] = std::max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}

int ST_query(int l, int r)
{
    int k = std::log2(r - l + 1);
    return std::max(f[l][k], f[r - (1 << k) + 1][k]);
}

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

    int n, m, k = 0;

    std::cin >> n >> m;
    for (int i = 1; i <= n; i++)
        std::cin >> a[i];

    ST_creat(n);

    for (int i = 1; i <= m; i++)
    {
        int l, r;
        std::cin >> l >> r;
        std::cout << ST_query(l, r) << "\n";
    }

    return 0;
}

算法分析

创建 ST 表时,初始化需要 \(O(n)\) 时间,两个 for 循环需要 \(O(n \log n)\) 时间,总时间复杂度为 \(O(n \log n)\)

查询时,计算 \(k\) 值后从表中取两个数并取最大值,因此时间复杂度为 \(O(1)\)

posted @ 2024-01-07 11:17  lzy20091001  阅读(46)  评论(0)    收藏  举报