算法学习笔记(6)-- ST表

一、简介

前面学习过无修线段树维护最大值,但是线段树写起来代码量大点。

ST表是一种主要用来解决 区间最大/最小值查询 问题的数据结构,它可以做到 O(n*log(n)) 预处理,O(1) 查询。其中主要运用到倍增的思想。

除此之外,Pecco还说过:

二、算法原理

1.倍增

比如我们想在一个数组上快速移动到某个位置,那么一步步自增下标(++ i)肯定是最慢的。

因为一般的数据量都在 1e6 左右,而 1e6 的 log2 是 20,所以利用二进制来跳跃,可以极大地缩短时间。具体操作是遍历每一种 pow(2, i),其中 i = 1, 2, 3, ....., 20。

显然倍增不太可能准确的跳跃到我们需要的位置,但是已经非常接近了,减少了很多遍历的时间。

2.ST原理

预处理出 max[n][m],查询时直接查询。

(1)理解 max[n][m],看下面一张图:

假设我们有区间 [a, b] 和 [c, d] 的最大值,是不是能求出区间 [L, R] 的最大值。

如果我们能用数组记录 max[a][b] 和 max[c][d] 就好了,但是绝大多数情况下,1e6的数据都不允许我们这样的存储。

因此,ST表利用了倍增的思想,把第二个参数作为延申的距离。例如,max[n][m] 代表以 n 为起点,直到 n + pow(2, m) - 1 结束的最大值,也就是代表了区间 [n, n + pow(2, m) - 1] 的最大值。

(2)如何查询最大值

假设最后的答案是在上图所示的区间中获得,那么为了保证 [a, b] 和 [c, d] 一定能有交集,我们令 L + pow(2, x) = R,解出 x  = log2(R - L + 1)。可以证明这样一定有交集。

(3)如何维护 max[i][j]

我们知道 max[i][j] 代表的是 [i, i + pow(2, j) - 1],又根据倍增的思想,我们可以用 max[i][j - 1] 来更新 max[i][j]。即将 [i, i + pow(2, j) - 1] 分成两段由 [i][j - 1] 组成的区间,取他们的最大值。

 (4)剩下的一点问题

可能有铜学有点疑问,为什么是开区间 [i, i + pow(2, j)) ,而不能是闭区间 [i, i + pow(2, j)]。

在ST表中,主要是为了能使用题目给的数组来进行预处理。如果是闭区间,就会发现 max[i][0] 代表了 [i, i + 1],这时候查询 [i, i] 就会出错。因此需要使用开区间,即 [i, i + pow(2, j) - 1]。

至此,ST表讲解完成。

三、例子

URL:https://www.luogu.com.cn/problem/P2880

AC代码:

#include "bits/stdc++.h"

int n, q, max_f[50007][21], min_f[50007][21], lg2[50007];

signed main() {
	std::cin >> n >> q;
	for (int i = 1; i <= n; ++ i) {
		int x; std::cin >> x;
		max_f[i][0] = x;
		min_f[i][0] = x;
	}

	for (int i = 1; i <= 20; ++ i) {
		for (int j = 1; j + (1 << i) - 1 <= n; ++ j) {
			max_f[j][i] = std::max(max_f[j][i - 1], max_f[j + (1 << (i - 1))][i - 1]);
			min_f[j][i] = std::min(min_f[j][i - 1], min_f[j + (1 << (i - 1))][i - 1]);
		}
	}

	for (int i = 2; i <= n; ++ i) {
		lg2[i] = lg2[i / 2] + 1;
	}

	for (int i = 1; i <= q; ++ i) {
		int l, r; std::cin >> l >> r;
		int s = lg2[r - l + 1];
		int min = std::min(min_f[l][s], min_f[r - (1 << s) + 1][s]);
		int max = std::max(max_f[l][s], max_f[r - (1 << s) + 1][s]);
		std::cout << max - min << "\n";
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值