线段树(数组实现)
简介
线段树是一种用于处理区间查询和更新问题的数据结构。其关键思想在于使用树中的一个节点表示一个区间的信息,父节点表示的区间从中间位置划分为两个子节点表示的区间。用表示要查询/更新的区间,表示树中的某个节点表示的区间,当前者完全包括了后者时,说明本次操作可以直接通过对某个单一节点操作完成。在完成操作后,在递归的”归“过程中更新查询结果/父节点的信息,直到根节点。
可以直接用数组存储这棵树,根节点存储在下标1处,对于下标为id的当前节点,其左孩子的下标为2*id,右孩子的下标为2*id+1。
模板
为什么数组要开四倍空间:最理想情况即满二叉树时, 区间长度为1的叶子节点全部位于最底层,此时线段树中节点总数为;对于一般情况如上图,底层节点数应为大于等于n的最小的二次幂,总节点数应为:
基于数组的线段树实现如下,直接在已知原数组的基础上建立线段树(即单点更新):
1 | int n = arr.length; |
线段树(动态开点+懒更新)
简介
动态开点:对于数据分布比较稀疏的情况,基于数组的线段树可能会爆内存,这时可以使用Node形式实现线段树,采用动态开点策略,即只有要用到某节点时才新建该节点。
懒更新:懒更新策略即当树中节点表示的线段完全被当前操作区间包含时,无需继续向下递归,而是使用一个lazyMark将其标记,直到下方节点被真正使用时才使用lazyMark进行更新。如果线段树不采用懒更新策略,那么只有单点更新操作是O(logn)的,因为每次区间更新时,最坏的情况(例如更新根节点的值)需要遍历所有树中节点。
模板
以求区间最大值为例:
-
区间[l,r]代表要操作的区间,它在一次调用期间是永远不会被更改的
-
如果操作区间包含了当前区间[s,e],那么更新/返回最大值
-
如果操作区间与当前区间的左半有交集,那么进入到左侧线段;如果与右半有交集,那么进入到右侧线段
1 | /** |
线段树二分
在使用线段树得到了各个区间的信息后,可以利用这些信息进行二分。
例如本题2940. 找到 Alice 和 Bob 可以相遇的建筑 ,对于每个查询,查找满足 i > L 且 heights[i] > v的最小的下标 i ,这个查询可以使用线段树二分,线段树维护区间最大值,单次O(logn)的实现:
1 | private int query(int o, int l, int r, int L, int v) { |
题单
§线段树(无区间更新)
子数组中占绝大多数的元素 2205
最长递增子序列 II 2280
找到 Alice 和 Bob 可以相遇的建筑 2327 线段树二分
以组为单位订音乐会的门票 2470 线段树二分
由单个字符重复的最长子字符串 2629
§Lazy 线段树(有区间更新)
完成所有任务的最少时间 2381
更新数组后处理求和查询 2398
奇妙序列 2476 有更简单的做法
子数组不同元素数目的平方和 II 2816
LCP 05. 发 LeetCoin
LCP 27. 黑盒光线反射
§ 动态开点线段树
部分题目也可以用珂朵莉树解决。
掉落的方块
Range 模块
我的日程安排表 I
我的日程安排表 II
我的日程安排表 III
矩形面积 II
统计区间中的整数数目 2222
达到末尾下标所需的最大跳跃次数
__END__