Sparse Table
Sparse Table 可用于解决这样的问题:给出一个
例题:P2880 [USACO07JAN] Balanced Lineup G
有一个包含
个数的序列 ,有 次询问,每次询问 中最大值与最小值的差。
数据范围:。
分析:题目要求最大值和最小值的差难以直接求出,通常需要分别求解最大值和最小值。最直接的做法是每次遍历区间中的每一个数,记录最大值和最小值。这样可以正确求出正确答案,但是效率低下,时间复杂度高达
之所以这样做效率低下,是因为所有询问区间可能有着大量的重叠,这些重叠部分被多次遍历到,因此产生了大量的重复。如果可以通过预处理得到一些区间的最小值,再通过这些区间拼凑每一个询问区间,就可以提高效率。
预处理前缀和可以拼凑出任意区间的和,但是这个思路不能直接搬到最值查询问题中。原因在于区间和可以从一个大区间中减去一部分小区间得到,而区间最值不行,所以只能用小区间去拼出大区间。如何选择预处理的区间就成为关键,选择的区间既要能够拼出任意区间,数量少又不能太多,并且预处理和查询都要高效。
可以预处理以每一个位置为开头,长度为
void init() { for (int i = 1; i <= n; i++) { f[i][0] = h[i]; } for (int j = 1; (1 << j) <= n; j++) { for (int i = 1; i <= n - (1 << j) + 1; i++) { f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); } } }
接下来解决查询的问题,设需要查询最大值的区间是
int query(int l, int r) { int len = r - l + 1, ans = -INF, cur = l; for (int i = 0; (1 << i) <= len; i++) { if ((len >> i) & 1) { ans = max(ans, f[cur][i]); cur += (1 << i); } } return ans; }
更进一步,查询区间最值时,区间合并的过程允许重叠,因此只需要找到两个长度为
int query(int l, int r) { int k = log_2[r - l + 1]; // 可以预处理log_2的表 return max(f[l][k], f[r - (1 << k) + 1][k]); }
参考代码
#include <cstdio> #include <algorithm> using std::min; using std::max; const int N = 50005; const int LOG = 16; int h[N], f_min[N][LOG], f_max[N][LOG], log_2[N]; void init(int n) { log_2[1] = 0; for (int i = 2; i <= n; i++) log_2[i] = log_2[i >> 1] + 1; // 预处理对数表 for (int i = 1; i <= n; i++) { f_min[i][0] = f_max[i][0] = h[i]; } for (int j = 1; (1 << j) <= n; j++) { for (int i = 1; i <= n - (1 << j) + 1; i++) { f_min[i][j] = min(f_min[i][j - 1], f_min[i + (1 << (j - 1))][j - 1]); f_max[i][j] = max(f_max[i][j - 1], f_max[i + (1 << (j - 1))][j - 1]); } } } int query(int l, int r, int flag) { // flag为1时查询最大值,为0时查询最小值 int k = log_2[r - l + 1]; if (flag) return max(f_max[l][k], f_max[r - (1 << k) + 1][k]); else return min(f_min[l][k], f_min[r - (1 << k) + 1][k]); } int main() { int n, q; scanf("%d%d", &n, &q); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); init(n); for (int i = 1; i <= q; i++) { int a, b; scanf("%d%d", &a, &b); printf("%d\n", query(a, b, 1) - query(a, b, 0)); } return 0; }
Sparse Table 预处理部分的时间复杂度为
Sparse Table 不仅可以求区间最大值和最小值,还可以处理符合结合律和幂等律(与自身做运算,结果仍是自身)的信息查询,如区间最大公约数、区间最小公倍数、区间按位或、区间按位与等。
例题:P7333 [JRKSJ R1] JFCA
分析:看到环形,先破环成链。
看起来每个点的答案
对于每个点,二分答案,查询左右两段区间的最大值看是否大于等于
因为没有修改,所以区间最值可以用 Sparse Table 维护,总体时间复杂度为
参考代码
#include <cstdio> #include <algorithm> using std::max; using std::min; const int N = 300005; const int LOG = 17; int a[N], b[N], log_2[N], st[N][LOG], ans[N]; int query(int l, int r) { int len = log_2[r - l + 1]; return max(st[l][len], st[r - (1 << len) + 1][len]); } int main() { int n; scanf("%d", &n); for (int i = 2; i <= n; i++) log_2[i] = log_2[i / 2] + 1; for (int i = 1; i <= n; i++) { // 破环成链 scanf("%d", &a[i]); a[i + n * 2] = a[i + n] = a[i]; st[i][0] = st[i + n][0] = st[i + n * 2][0] = a[i]; ans[i] = n; } // Sparse Table 维护区间最大值 for (int j = 1; (1 << j) <= n; j++) { for (int i = 1; i <= 3 * n - (1 << j) + 1; i++) { st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } } for (int i = 1; i <= n; i++) scanf("%d", &b[i]); for (int i = n + 1; i <= n * 2; i++) { // left 二分左边第一个位置 int l = i - n + 1, r = i - 1, res = -1; while (l <= r) { int mid = (l + r) / 2; if (query(mid, i - 1) >= b[i - n]) { l = mid + 1; res = mid; } else { r = mid - 1; } } if (res != -1) ans[i - n] = min(ans[i - n], i - res); // right 二分右边第一个位置 l = i + 1; r = i + n - 1; res = -1; while (l <= r) { int mid = (l + r) / 2; if (query(i + 1, mid) >= b[i - n]) { r = mid - 1; res = mid; } else { l = mid + 1; } } if (res != -1) ans[i - n] = min(ans[i - n], res - i); } for (int i = 1; i <= n; i++) printf("%d ", ans[i] == n ? -1 : ans[i]); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?