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\) 后,对于给定的区间 \([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)\)。
本文来自博客园,作者:lzy20091001,转载请注明原文链接:https://www.cnblogs.com/lzy20091001/p/17950302/sparse-table

浙公网安备 33010602011771号