线段树
线段树
线段树是一种用来维护区间性质的数据结构,此篇不讲解基础,而是讲解在算法竞赛中的应用。读者需要至少会写线段树懒标记维护区间求和操作。
目前使用的模板
我的懒标记的定义是: 已经维护好了当前节点,但子节点还没维护好.
普通版本
1 | const int maxn = 1e5 + 5; |
取模版本
1 | const int maxn = 1e5 + 5; |
点修改版本
1 | typedef long long typec; |
万用模板
1 | template<typename T, class Lz> |
使用注意事项
区间范围
build(1, 1, n)
后面2个参数是要维护的范围可以根据题目选择,比如可以改成
build(1, 0, n)
读入优化
一般读入数据比较大,建议加上读入优化
1 | ios_base::sync_with_stdio(0); |
基础应用
区间求和类
- 区间求和
- 区间维护乘积与求和
- 区间覆盖问题
- 区间最大/最小值
- 区间反转问题
可以转换为区间求和再对2取模
解题技巧
维护2个线段树
注意不能用1个懒标记同时维护2个线段树,很容易错
通过区间加减来减少维护难度
剩余树苗=剩余树-没砍过的树
此题里面, 砍下的数苗=总种下数苗 - 剩余树苗
总种下树苗=每次种的树苗之和
每次种的树苗=种之后的树-种之前的树
加粗的部分可以用线段树维护,下划线是要求的
对查询操作建树
-
除之前的数相当于在序列上把那个值变成1, 然后求乘积即可
权值线段树
权值线段树是用线段树来维护某个元素出现的个数,由于值域范围通常较大,权值线段树会采用离散化或动态开点的策略优化空间。
离散化
1 | cin >> n; |
离散化的时候如果需要反映射可以加一个map
线段树的每个节点存储 \([l,r]\) 区间的数有多少个.
求比某个区间里的数有几个
-
不用开 \(n\) 个线段树,因为每个区间求过一次就不用再求了,所以可以从小到大依次使用
-
也是计数问题,注意在查询的时候
ql <= qr
不然会死循环. 这题也是线段树的重复使用。先统计左边,统计右边的时候依次把元素删除即可. -
这题需要推导一下. 先分析冒泡排序的性质。定义 \(d[i]\) 为前面比自己大的数的个数。发现每一轮冒泡排序,\(d[i]=0\) 的那些数会与后面比自己小的数交换(减少逆序对),直到比自己大的人出现。然后交换过的数的 \(d[i]\) 会减小1. (因为前面有一个比他大的数跑后面去了). 若 \(|x|\) 表示 \(x\) 的数量的话,所以每一轮减小逆序对的数量是 \(n - |d[i]=0|\) ,因为每排一轮 \(d[i]\) 会相应减小,所以下一轮\(d[i]=0\) 的数是前一轮 \(d[i]=1\) 的数. 那么第 \(k\) 轮 \(|d[i]=0|\) 就是第开始 \(|d[i]\leq k-1|\) 所以经过 \(k\) 轮排序减小逆序对的数量是 \[ \sum_{s=0}^{k-1}{n-|d[i]\leq s|} \\ =kn-\sum_{s=0}^{k-1}{|d[i]\leq s|} \] 若 \(ans\) 是开始总的逆序对数量,那么我们要求的 \(k\) 轮后逆序对的数量是 \[ ans -kn+\sum_{s=0}^{k-1}{|d[i]\leq s|} \] 其中后面那个求和可以用权值线段树来维护. 交换操作的话可以用点修改来维护.
模拟平衡树
需要离散化或者动态开点.
查询前驱后继可以用map的lower_bound
和upper_bound
1 |
|
线段树进阶
进阶的线段树可能需要维护节点的多个信息
分治思想
答案要么在左区间,要么在右区间,要么在中间.
最大子段和
设 \(ls\) 是区间左节点开始延伸的最大值,\(rs\) 是右节点向左延伸的最大值,\(ss\) 是区间的总最大值,\(sum\) 是区间所有元素和.
维护的时候
- \(ls = max(ls_{lson}, sum_{lson} + ls_{rson})\)
- \(rs = max(rs_{rson}, sum_{rson} + rs_{lson})\)
- \(sum = sum_{lson} + sum_{rson}\)
- \(ss = max(ss_{lson}, ss_{rson}, rs_{lson} + ls_{rson})\)
1 |
|
与最大子段和类似,但是在标签转移的时候考虑条件. 只有不相等的时候才转移。注意的是这题不需要把所有状态(给左右是L,R的情况都定义对应的标签)都定义出来,那样会非常复杂。可以把0和1的两种情况综合起来,(因为2个标签必然有1个是0),只需要考虑端点处是否不相等。
1 |
|