算法笔记-ST表

//2024.4.9 初稿,后续会完善更新。

//2024.4.10 第一次修改

//2024.4.11 第二次修改

引入

如果给你一个数组,要你输出[l, r]区间中的最大值,你会怎么做?

我会线段树

抛开线段树不谈,按正常的操作是[l, r]上遍历一遍暴力取最大,但是这太不方便了,智慧的人类想到了能够O(1)查询的方法,但是这容忍了区间的重复----ST表

什么时候会用到

ST表是解决 可重复贡献问题 的数据结构,
常见的有区间值、区间按位和、区间按位或、区间GCD等。
用在静态,区间,可重复问题上。

什么叫可重复贡献度? 比如要你求 10 个数的最大值,你完全可以先求前 6 个的最大值,然后再求后 7 个的最大值,然后两个取最大。在此过程中有些数被重复利用到了,这就是可重复贡献度问题。

构建

st[i][j]代表着从第 i 号点开始, 长度为 2^{j} 的区间的性质。

以按位与为例,

构建

inline void build() {
	for (int i = 1; i <= n; i ++ ) st[i][0] = a[i];
	for (int j = 1; (1 << j) <= n; j ++ ) {
		for (int i = 1; i + (1 << j - 1) <= n; i ++ ) {
			st[i][j] = st[i][j - 1] & st[i + (1 << j - 1)][j - 1];
		}
	}
}

首先,st[i][0]代表长度为 0, 那么就是单个数据的本身性质。

类似于线段树的pushup操作,j 是从 两个 j - 1上推出来的

这里的 j 指代的是区间的长度,

j 最大枚举到 lg2(n)即可,因为查询的时候,用两个lg2(n)的长度的区间就可以覆盖整个区间。

[l,                                        r]

[----------------                        ]

[              ------------------------]

而下面的 i + (1 << j - 1) 是要确保第二个点在区间内,因为我们不需要在区间外的点作为st[i]中的i

然后我们开始构建,有转移方程:

f(i, j) = (f(i, j - 1), f(i + 2^{j - 1}, j - 1))

开的大小:第一维开区间长度,第二维开lg2(n)即可,这是因为我们用倍增进行了优化,如果按正常存储,我们需要开st[i][2^{j}]的空间,这样会爆的。

查询

对于每个询问[l, r], 我们将其分成[l, l + 2^{s} - 1][r - 2^{s} + 1, r]两部分进行查询。

(其中s = \left \lfloor log2(r - l + 1) \right \rfloor

这样的s会有重叠,但是重叠对答案并无影响。

下面的式子取出了[l, r]上的按位与。

inline void query(int l, int r) {
	int s =  lg2[r - l + 1];
	return st[l][s] & st[r - (1 << s) + 1][s];
}

lg2预处理

我们会需要用到lg2,直接预处理可以节省时间。

inline void pre_work() {
	for (int i = 2; i <= n; i ++ ) lg2[i] = lg2[i >> 1] + 1;
}

相关题目

Problem - E - Codeforces

模板题+代码:

模板题(洛谷)

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
using ll = long long;
const int N = 1e5 + 9;
int n, m, a[N], lg2[N], st[N][20];
inline void build() {
	for (int i = 1; i <= n; i ++ ) st[i][0] = a[i];
	for (int j = 1; (1 << j) <= n; j ++ ) {
		for (int i = 1; i + (1 << j - 1) <= n; i ++ ) {
			st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
		}
	}
}
inline void pre_work() {
	for (int i = 2; i <= 1e5; i ++ ) lg2[i] = lg2[i >> 1] + 1;
}
inline int query(int l, int r) {
	int s = lg2[r - l + 1];
	return max(st[l][s], st[r - (1 << s) + 1][s]);
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	pre_work(), build();
	while (m -- ) {
		int l, r; cin >> l >> r;
		cout << query(l, r) << endl;
	}
	return 0; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值