专栏文章
区间数据结构从入门到普及-
算法·理论参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mip1f3u5
- 此快照首次捕获于
- 2025/12/03 04:35 3 个月前
- 此快照最后确认于
- 2025/12/03 04:35 3 个月前
前言
最近学文化课学傻了,来复习一下数据结构。
能够实现区间操作的数据结构其实并不多,平常也就能用到这几个:
- ST 表
- 树状数组
- 线段树
- 分块
今天我就只说这几个,方便新手入门。如果你是学数据结构的新手,一定一定要循序渐进,这几个数据结构是提高组很常考的考点。
ST 表
例题 1:P3865
题目大意
给定长度为 的序列 ,以及 组询问,每次询问给出 ,求 。,。
题解
题目要求每次查询 ,最简单的想法就是预处理每个区间的最大值,这样时间确实合理,但空间复杂度 ,直接炸掉。
我们确实需要预处理,但预处理方式有不同。我们学校古时候有个 OIer 叫做 CEXE 的说过,“题目中有预处理,要么倍增,要么 dp”。根据倍增思想,我们定义 为从第 个元素开始的 个元素中的最大值。 的遍历范围明显是 到 , 一般从 到 。
接着推转移方程。根据倍增的特点,长度为 的区间可以由两个长度为 的区间拼起来。由于 从小到大遍历,长度为 的两个区间最大值已经知道,对这两个区间的两个最大值再取一个更大的即可。
这两个区间的最大值都该怎么获取呢?左边区间的起点显然与当前区间的起点相同,长度为 ;右边区间的起点是左边区间的终点加一,左边区间的终点是 ,所以右边区间的起点就是 ,长度依然为 。我们就可以得到状态转移方程了。
把它变成代码,就是
CPPf[i][j]=max(f[i][j-1],f[min(i+(1<<(j-1)),n)][j-1]);
注意右边区间的起点可能超过 ,要与 取一个更小值。
最后,不要忘记 数组的初始化。不难想到 ,即从 开始的 个元素中的最大值就是 。后面的代码中我直接把输入内容存到了 中,没有开 数组。
预处理完了,来想如何 算出答案。我们可以想到,对于询问区间 ,可以把它拆成两个可以部分重叠的区间,一个从 开始往前覆盖,一个从 开始往后覆盖。由于重叠的部分对区间最大值没有影响,所以不用处理重叠部分。
例如,对于序列 ,假设我们已经预处理完了 数组,想要求 区间内的最大值。我们知道 数组如下:
我们令 。这有什么用呢?这表示我们可以用 和 来凑出 区间的最大值。例如 的最大值就是 。写成代码,就是
CPPmax(f[l][k],f[r-(1<<k)+1][k])
想要求出 ,我们可以使用
CPPcmath 头文件自带的函数 log2() 来取对数。注意函数返回值是 double 类型,需要强制转化成 int 类型,这样省下了一个下取整。int k=log2(r-l+1);
最后的代码实现还有细节,由于我们要从长度小的区间推到长度大的区间,我们需要保证长度小的区间都预处理完成,所以我们必须先枚举 再枚举 。
下面是模板题 P3865 的主要代码。
CPPcin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i][0];
}
for(int j=1;j<20;j++){
for(int i=1;i<=n;i++){
a[i][j]=max(a[i][j-1],a[min(i+(1<<j-1),n)][j-1]);
}
}
while(m--){
cin>>l>>r;
int k=log2(r-l+1);
cout<<max(a[l][k],a[r-(1<<k)+1][k])<<endl;
}
最后分析时间复杂度。预处理的复杂度为 ,每次查询复杂度 , 次共 ,总复杂度 ,可以通过本题。
还有一个细节,
log2() 函数的复杂度稍大于 ,但一般忽略。要是想做到严格 ,可以预处理出每个 的值。相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...