线段树概述:

  • 每个节点表示一段区间
  • 具有唯一根节点,表示整个统计范围
  • 每个叶节点代表一个长度为1的元区间

image.png

image.png

基本操作

  1. pushup(int u)

由子节点算父节点信息,例如sum = l.sum + r.sum

  1. build() 将一段区间初始化为线段树

用一维数组存储整棵树,类似于堆。
编号为x的节点

  1. - 父节点 `x / 2` `x >> 1`
  2. - 左儿子 `2x` `x << 1`
  3. - 右儿子 `2x` `x << 1 | 1`
  1. modify()
    1. 修改一个点
    2. 修改一段区间 pushdown() ,即懒标记/延迟标记。

将父节点的信息更新到子节点

  1. query() 查询某一段区间的信息
  2. pushdown()操作

灵感来自于query操作,新增add字段作为懒标记
给以当前节点为根的子树中的每一个节点加上add(不包含根节点)
相应的,使用懒标记后查询操作也得发生变化,先pushdownquery,修改操作先pushdownmodify
一个疑问:为什么modify的时候也需要先pushdown? 如果被打上懒标记的区间的一个子区间被修改,势必会执行pushup操作,而根区间由于懒标记并未向下传递会导致子区间在原来的数值上修改并影响了跟区间,从而导致懒标记毫无作用。

  1. //初始化
  2. N = <输入规模>
  3. Node[] tr = new Node[4 * N];
  4. build(int u, int l, int r) {
  5. tr[u] = new Node(l, r);
  6. if (l == r) {
  7. tr[u]的其它属性进行赋值操作
  8. }
  9. int mid = l + r >> 1;
  10. build(u << 1, l, mid);
  11. build(u << 1 | 1, mid, r);
  12. pushup(u);
  13. }
  14. //pushup
  15. 这个操作时间复杂度一般为O(1)
  16. 应用在递归回退的过程中用子节点更新父节点
  17. //查询
  18. /*
  19. 待查询区间为[l, r], 当前线段树节点区间为[Tl, Tr]
  20. ①:[Tl, Tr] ∈[l, r] 直接返回
  21. ②:[Tl, Tr] ∩ [l, r] != 空 //一定成立,因为不会遍历那些没有交集的节点
  22. 时间复杂度约为4lgn,n指待查询区间(r - l + 1)
  23. */
  24. //单点修改
  25. 只需要从上往下递归遍历某一分支,时间复杂度为O(lgn)

原理

整棵线段树是一棵二叉树,并且除了最后一层,是一个满二叉树。
树的深度为O(logN),可以用类似堆的方法,用数组存储线段树。
分界点取法
mid = l + r >> 1 左半: [l, mid] 右半: [mid + 1, r]
编号为x,父节点为 x >> 1 ,左儿子为 x << 1 ,右儿子为 x << 1 | 1
存储空间开多大?
4 * n
4N 即全树节点数估计,区间长度为N的线段树,理想情况下N个叶节点的满二叉树有N + N / 2 + N / 4 + ... + 1 = 2N - 1个节点。使用数组存储时最后一层会有空余,所以保存线段树的数组长度要不小于4N保证不会越界。
image.png

问题类型

  1. 单点修改,区间查询

Acwing 1275. 最大数

  1. 维护懒标记,区间修改,区间查询

AcWing 243. 一个简单的整数问题2
AcWing 1277. 维护序列

  1. 扫描线

AcWing 247. 亚特兰蒂斯

【hdu1542】线段树求矩形面积并

  1. 动态开点线段树

715. Range 模块

699. 掉落的方块
注意本题有点坑:某段区间变高是在添加之前的最高一段区间的基础上加的

方法1:面向对象动态开点

  1. class Solution {
  2. public List<Integer> fallingSquares(int[][] positions) {
  3. Node root = new Node(1, (int)(1e9));
  4. List<Integer> res = new ArrayList<>();
  5. for (int[] p : positions) {
  6. int h = root.query(p[0], p[0] + p[1] - 1);
  7. root.modify(p[0], p[0] + p[1] - 1, p[1] + h);
  8. res.add(root.height);
  9. }
  10. return res;
  11. }
  12. }
  13. class Node {
  14. int l, r;
  15. int height, add;
  16. Node left, right;
  17. Node(int l, int r) {
  18. this.l = l;
  19. this.r = r;
  20. }
  21. int query(int l, int r) {
  22. if (l <= this.l && r >= this.r)
  23. return height;
  24. else {
  25. int mid = this.l + this.r >> 1;
  26. if (this.left == null) {
  27. this.left = new Node(this.l, mid);
  28. }
  29. if (this.right == null) {
  30. this.right = new Node(mid + 1, this.r);
  31. }
  32. pushdown();
  33. int res = 0;
  34. if (l <= mid) res = left.query(l, r);
  35. if (r > mid) res = Math.max(res, right.query(l, r));
  36. return res;
  37. }
  38. }
  39. void modify(int l, int r, int x) {
  40. if (l <= this.l && r >= this.r) {
  41. this.height = x;
  42. this.add = x;
  43. } else {
  44. int mid = this.l + this.r >> 1;
  45. if (this.left == null) {
  46. this.left = new Node(this.l, mid);
  47. }
  48. if (this.right == null) {
  49. this.right = new Node(mid + 1, this.r);
  50. }
  51. pushdown();
  52. if (l <= mid) this.left.modify(l, r, x);
  53. if (r > mid) this.right.modify(l, r, x);
  54. pushup();
  55. }
  56. }
  57. void pushup() {
  58. height = Math.max(right.height, left.height);
  59. }
  60. void pushdown() {
  61. if (add > 0) {
  62. left.height = add;
  63. left.add = add;
  64. right.height = add;
  65. right.add = add;
  66. add = 0;
  67. }
  68. }
  69. }

方法2:面向过程动态开点
预估节点数为查询次数 6 log(n),n为总区间大小

  1. class Solution {
  2. class Node {
  3. int idxl, idxr;
  4. int height;
  5. int add;
  6. }
  7. Node[] tr = new Node[240010];
  8. int idx = 1;
  9. public List<Integer> fallingSquares(int[][] positions) {
  10. List<Integer> res = new ArrayList<>();
  11. for (int[] p : positions) {
  12. int h = query(1, 1, (int)(1e9), p[0], p[1] + p[0] - 1);
  13. modify(1, 1, (int)(1e9), p[0], p[0] + p[1] - 1, p[1] + h);
  14. res.add(tr[1].height);
  15. }
  16. return res;
  17. }
  18. void modify(int u, int l, int r, int ql, int qr, int x) {
  19. if (ql <= l && qr >= r) {
  20. tr[u].height = x;
  21. tr[u].add = x;
  22. } else {
  23. int mid = l + r >> 1;
  24. creatNode(u);
  25. pushdown(u);
  26. if (ql <= mid) modify(tr[u].idxl, l, mid, ql, qr, x);
  27. if (qr > mid) modify(tr[u].idxr, mid + 1, r, ql, qr, x);
  28. pushup(u);
  29. }
  30. }
  31. int query(int u, int l, int r, int ql, int qr) {
  32. if (ql <= l && qr >= r) {
  33. return tr[u].height;
  34. } else {
  35. int mid = l + r >> 1;
  36. creatNode(u);
  37. pushdown(u);
  38. int res = 0;
  39. if (ql <= mid) res = Math.max(res, query(tr[u].idxl, l, mid, ql, qr));
  40. if (qr > mid) res = Math.max(res, query(tr[u].idxr, mid + 1, r, ql, qr));
  41. return res;
  42. }
  43. }
  44. void pushup(int u) {
  45. tr[u].height = Math.max(tr[tr[u].idxl].height, tr[tr[u].idxr].height);
  46. }
  47. void pushdown(int u) {
  48. if (tr[u].add > 0) {
  49. Node left = tr[tr[u].idxl], right = tr[tr[u].idxr];
  50. left.height = tr[u].add;
  51. left.add = tr[u].add;
  52. right.height = tr[u].add;
  53. right.add = tr[u].add;
  54. tr[u].add = 0;
  55. }
  56. }
  57. void creatNode(int u) {
  58. if (tr[u] == null)
  59. tr[u] = new Node();
  60. if (tr[u].idxl == 0) {
  61. tr[u].idxl = ++idx;
  62. tr[idx] = new Node();
  63. }
  64. if (tr[u].idxr == 0) {
  65. tr[u].idxr = ++idx;
  66. tr[idx] = new Node();
  67. }
  68. }
  69. }

线段树.pdf

  • 挖个坑,非递归线段树

Efficient and easy segment trees
算法学习笔记(76): zkw线段树

参考

2019年8月XX日线段树讲义
https://codeforces.com/blog/entry/18051