线段树从入门到急停 
感谢官方推荐  🎉😄。
可在作者的 github仓库  中获取本文和其他文章的 markdown 源文件及相关代码。
 
欢迎评论或仓库 PR 指出文章错漏或与我讨论相关问题,我将长期维护所有文章。
 
所有文章均用 Typora 完成写作,可使用 Typora 打开文章 md 文件,以获得最佳阅读体验。
 
 
 
⚠️ ⚠️ ⚠️ 谨以此两万五千字文章献给在线段树门前徘徊的朋友。 
❗️ 【NEW】最新文章如下  ❗️
这是小白 yuki 推出的「树ADT」系列文章的第 11 篇 (11/13) 。
k e y w o r d s keywords k ey w or d s 
完全二叉树下标性质 / 分治算法 / 单点查询 (维护或不维护 n u m s nums n u m s a d d add a dd u p d a t e update u p d a t e 
 
线段树  是著名的用于高效求解 「区间问题」  的数据结构。「区间问题」即对于输入数组 n u m s nums n u m s 「区间求和」  、 「区间修改」  等操作,通常还伴随着针对单个元素的 「单点查询」  、 「单点修改」  这两种单点操作。若直接操作 n u m s nums n u m s O ( 1 ) O(1) O ( 1 ) O ( n ) O(n) O ( n ) O ( 1 ) O(1) O ( 1 ) O ( n ) O(n) O ( n ) n u m s nums n u m s d f s dfs df s 同时实现 O ( l o g n ) O(logn) O ( l o g n )   。
本文行文过程中,我会将线段树与另一种求解区间问题的数据结构 –– 「树状数组」做对比。虽然「线段树」与「树状数组」在原理上差别不小,但 「区间划分」  的思想是一致的。「树状数组」的特点是思维难度大,实现简单,「线段树」正好相反,思维难度低,实现相对复杂,且对于区间问题,线段树比树状数组更具普适性。不过我仍然建议你在阅读本文之前先阅读 树状数组  一文,即便暂时理解不了树状数组中 l o w b i t lowbit l o w bi t 
本文将给出 十种  线段树的完整类实现代码 (静态 & 动态),可应对力扣中出现的 (几乎) 所有能用线段树解决的题目  。并在「实战应用」中给出近十道力扣上的线段树题目及详细题解 (持续增加中)。
另外,本文原题 「线段树 (树ADT连载 11/13)」,十分干瘪,不太符合作者的气质,遂改为现标题。「急停」表示作者的一种希望 ,我猜想许多朋友跟作者一样,在求索线段树的路上狼奔豕突,相关文章和题解看了不少,却始终不得其法。现在,作者希望朋友们能够通过本文实现完美急停,此后 从容吟啸且徐行 。
yuki的其他文章如下,欢迎阅读指正!
如下所有文章同时也在我的 github 仓库  中维护。
 
[2022-09-23]
[2022-09-22]
增加了「区间最值查询」小节,替换了一张错误图片,另有若干词句修改。 
 
[TOC]
线段树 
我们已经知道,针对序列上的「区间操作」,相比普通数组和前缀和数组,基本树状数组 (PURQ BIT) 对「单点修改」及「区间查询」这两种操作实现了平衡,即均为 O ( l o g n ) O(logn) O ( l o g n ) O ( l o g n ) O(logn) O ( l o g n ) 将区间内元素修改为同一元素时  ,或者求给定区间的 区间最大/最小值  时,三种树状数组均不能以 O ( l o g n ) O(logn) O ( l o g n ) 
对于这些需求,线段树都能够以 O ( l o g n ) O(logn) O ( l o g n ) 
线段树 (Segment Tree) : 线段树是一种用以支持序列区间操作的数据结构,相比基本树状数组,能够支持的操作种类更多,因此对一般的序列区间问题更具 普适性  。
在后续内容中,我们先通过 「完全二叉树下标性质」  和 「分治算法」  来理解基本线段树的工作原理。在掌握了支持「单点修改」和「区间查询」的基本线段树后,引入  「懒惰标记」  和 「延迟修改」  的概念,用以实现 「区间修改」。在给出带懒惰标记的线段树实现后,我们马上尝试解决  699. 掉落的方块  ,为解决此题需借用在「树状数组」中介绍过的 「离散化」  方法,我将给出 「松离散」  和 「紧离散」  两种离散化实现 (我自己瞎命名的)。接着尝试解决  715. Range 模块  ,并发现该题具有「强制在线 」的特点,由于无法离散化,这要求我们实现能够 「动态开点」   (动态地创建结点) 的线段树。根据是否可以提前估计树的大小,我们将分别介绍  「结点数组法动态开点线段树」  以及 「结点指针 (引用) 法动态开点线段树」  ,在给出它们的完整的类代码后,演示如何将其用于解决 699 题以及 715 题。
在讲解过程中,我会给出十种线段树的完整类代码,基本能够覆盖力扣上的所有线段树题目。
线段树由 Jon Bentley 于1977年发明。
The segment tree was invented by Jon Bentley  in 1977; in “Solutions to Klee’s rectangle problems”.[7] 
作者的「线段树」知识,最初学自 OI wiki 线段树  。
 
基本线段树 
如果我们只要求线段树像基本树状数组 (PURQ BIT) 那样只需支持「单点修改」和「区间查询」,那么我们将得到最基本的线段树。
在「树状数组」中我们从如何提高区间查询的效率这一问题入手,提出了将原数组 n u m s nums n u m s O ( l o g n ) O(logn) O ( l o g n ) l o w b i t lowbit l o w bi t 更一般  的方法能够将 n u m s nums n u m s 更直观  地组成任意区间 (直接结合而非前缀区间作差),且仍能通过下标的某些性质来操作结点呢 (目的是通过下标定位到需要的区间和结点) ?
根据树结点下标性质来操作结点这一要求中,我们嗅到了 「完全二叉树」  的味道。在完全二叉树中,结点 i i i 2 ∗ i 2*i 2 ∗ i 2 ∗ i + 1 2*i+1 2 ∗ i + 1 
线段树的形态 
假设需要在输入数组 n u m s nums n u m s n u m s nums n u m s 
首先,既然是完全二叉树,那么这棵线段树可以与一个数组对应,数组的一个元素对应一个树的一个结点。树的一个结点代表某个区间的区间和,我们用 t r e e [ ] tree[] t ree [ ] 
 
更大的区间总是由更小区间构成,因此代表 n u m s nums n u m s t r e e [ x ] tree[x] t ree [ x ] 应当在树的最底层  ,即线段树的每一个叶子结点都与 n u m s nums n u m s t r e e [ x ] = n u m s [ i ] tree[x] = nums[i] t ree [ x ] = n u m s [ i ] 
 
更大的区间是从叶子结点开始向上构成的,例如代表 n u m s [ 0 ] nums[0] n u m s [ 0 ] n u m s [ 1 ] nums[1] n u m s [ 1 ] [ 0 , 1 ] [0,1] [ 0 , 1 ] 
 
查询区间 [ l , r ] [l,r] [ l , r ] l , r l,r l , r n u m s nums n u m s 形如完全二叉树的线段树的结点标号  相联系,这个「完全二叉树结点下标性质」我们很熟悉,将数组 n u m s nums n u m s n u m s nums n u m s n u m s nums n u m s 
 
 
在熟知完全二叉树下标性质的基础上,上述分析是简单的。t r e e [ i ] tree[i] t ree [ i ] i i i t r e e [ 1 ] tree[1] t ree [ 1 ] n u m s nums n u m s t r e e [ 2 ] tree[2] t ree [ 2 ] [ 0 , n − 1 2 ] [0, \frac{n-1}{2}] [ 0 , 2 n − 1  ] t r e e [ 3 ] tree[3] t ree [ 3 ] [ n − 1 2 + 1 , n − 1 ] [\frac{n-1}{2}+1, n-1] [ 2 n − 1  + 1 , n − 1 ] 结点标号总是与该结点代表的区间一一对应  。
代表区间 [ l , r ] [l,r] [ l , r ] t r e e [ i ] tree[i] t ree [ i ] t r e e [ 2 ∗ i ] tree[2*i] t ree [ 2 ∗ i ] [ l , l + r 2 ] [l,\frac{l+r}{2}] [ l , 2 l + r  ] t r e e [ 2 ∗ i + 1 ] tree[2*i+1] t ree [ 2 ∗ i + 1 ] [ l + r 2 + 1 , r ] [\frac{l+r}{2}+1,r] [ 2 l + r  + 1 , r ] 
 
于是我们很容易得到线段树的一般表示,下图表示大小为 15 的 n u m s = { a 0 , a 1 , . . . , a 14 } nums=\{a0,a1,...,a14\} n u m s = { a 0 , a 1 , ... , a 14 } [ l , r ] , l , r ∈ [ 0 , n − 1 ] [l,r], l,r∈[0,n-1] [ l , r ] , l , r ∈ [ 0 , n − 1 ] l l l r r r 
了解了线段树的区间划分、结点所代表的具体区间与结点下标的关系后,接下来我们给出基本线段树的「初始化」、「单点修改」以及「区间查询」实现。在这之前先简单分析线段树的大小。
线段树的大小 
对于长度为 n n n n u m s nums n u m s t r e e [ ] tree[] t ree [ ] t r e e [ ] tree[] t ree [ ] n n n m m m 根据线段树为一棵完全二叉树的特点来寻找 n n n m m m   。证明见 oi-wiki 线段树  ,作者未完全看懂 m m m m = 4 ∗ n − 5 m=4*n-5 m = 4 ∗ n − 5 如果读者能提供易懂的数学证明,盼赐教 。
总之,在实际使用时,我们总是不精确地令 m = 4 ∗ n m=4*n m = 4 ∗ n 
初始化 
如果读者熟悉归并排序和快速排序这类 分治算法  的「对原问题域递归地划分为左右子问题域」的操作,那么线段树的主要方法都将是简单的。我们直接给出如下线段树类 S e g m e n t T r e e SegmentTree S e g m e n tT ree 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class  SegmentTree  {    int [] nums, tree;     int  n;     public  SegmentTree (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .tree = new  int [4  * n];          build(0 , n - 1 , 1 );     }     private  void  build (int  s, int  t, int  i) {          if (s == t) {              tree[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ];     } } 
构造器通过 build(0, n - 1, 1) 完成线段树的构建 (初始化 t r e e [ ] tree[] t ree [ ] b u i l d build b u i l d n u m s nums n u m s 区间和结点的下标总是和该区间的左右界一起被传入  。该写法实际上就是大家十分熟悉的二叉树的「后序遍历」写法。
递归的基准情形是根据 s == t 判断到达叶子结点后,令 tree[i] = nums[s] ,使得每个叶子结点从左到右对应 n u m s nums n u m s tree[i] = tree[i * 2] + tree[i * 2 + 1]  自底向上地初始化所有区间结点的区间和。这行语句在线段树的实现中较常用,我们可以将它封装为一个辅助方法 p u s h U p pushUp p u s h U p 
1 2 3 4 private  void  pushUp (int  i) {     tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ]; } 
后面我们将看到,线段树的所有主要方法的执行过程,都是类似这样的 二叉树后序遍历的递归过程  ,读者一定会感到线段树不同方法的写法如出一辙,简单得令人惊讶。
单点修改 
单点修改有两种,增量式修改,即加上某值  (记为 a d d add a dd n u m s [ i ] + = x nums[i]+=x n u m s [ i ] + = x 覆盖式修改,即改为某值  (记为 u p d a t e update u p d a t e n u m s [ i ] = x nums[i]=x n u m s [ i ] = x 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public  void  add (int  i, int  x) {     add(i, x, 0 , n - 1 , 1 ); } public  void  update (int  i, int  x) {    update(i, x, 0 , n - 1 , 1 ); } private  void  add (int  idx, int  x, int  s, int  t, int  i) {    if (s == t) {         tree[i] += x;          return ;     }     int  c  =  s + (t - s) / 2 ;     if (idx <= c) add(idx, x, s, c, i * 2 );     else  add(idx, x, c + 1 , t, i * 2  + 1 );     pushUp(i); } private  void  update (int  idx, int  x, int  s, int  t, int  i) {    if (s == t) {         tree[i] = x;          return ;     }     int  c  =  s + (t - s) / 2 ;     if (idx <= c) update(idx, x, s, c, i * 2 );     else  update(idx, x, c + 1 , t, i * 2  + 1 );     pushUp(i); } 
a d d add a dd u p d a t e update u p d a t e n u m s nums n u m s n u m s nums n u m s 
1 2 3 4 5 6 7 8 9 10 11 public  int  query (int  i) {     return  query(i, 0 , n - 1 , 1 ); } private  int  query (int  idx, int  s, int  t, int  i) {    if (s == t) return  tree[i];     int  c  =  s + (t - s) / 2 ;     if (idx <= c) return  query(idx, s, c, i * 2 );     else  return  query(idx, c + 1 , t, i * 2  + 1 ); } 
当然我们也可以实时地维护 n u m s nums n u m s a d d add a dd u p d a t e update u p d a t e a d d add a dd n u m s [ i ] nums[i] n u m s [ i ] n u m s [ i ] nums[i] n u m s [ i ] 
1 2 3 4 5 6 7 8 public  void  update (int  i, int  x) {     add(i, x - nums[i], 0 , n - 1 , 1 );     nums[i] = x;  } public  int  query (int  i) {     return  nums[i]; } 
实际上,由于单点查询和单点修改都可以视作区间长度为 1 的区间查询和区间修改。上述方法也可以由区间查询和区间修改代替。
区间查询 
区间查询 (求和) 也是简单的。与单点操作不同的是,区间和需要累积,因此 if(l <= c) 与 if(r > c) 是并列关系。递归的基准情形为 if(l <= s && t <= r) ,表示当前递进到的区间 [ s , t ] [s,t] [ s , t ] [ l , r ] [l,r] [ l , r ] t r e e [ i ] tree[i] t ree [ i ] 
1 2 3 4 5 6 7 8 9 10 11 12 public  int  sum (int  l, int  r) {     return  sum(l, r, 0 , n - 1 , 1 ); } private  int  sum (int  l, int  r, int  s, int  t, int  i) {    if (l <= s && t <= r) return  tree[i];      int  c  =  s + (t - s) / 2 , sum = 0 ;     if (l <= c) sum += sum(l, r, s, c, i * 2 );      if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );      return  sum; } 
也可以查询区间最值,以区间最小值为例,如下。
1 2 3 4 5 6 7 8 9 10 11 12 public  int  min (int  l, int  r) {     return  min(l, r, 0 , n - 1 , 1 ); } private  int  min (int  l, int  r, int  s, int  t, int  i) {    if (s == t) return  tree[i];      int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;     if (l <= c) lmin = min(l, r, s, c, i * 2 );     if (r > c) rmin = min(l, r, c + 1 , t, i * 2  + 1 );     return  Math.min(lmin, rmin); } 
区间最值查询 
需要注意的是 ,对于「区间最值查询」,上面给出的 m i n min min t r e e [ ] tree[] t ree [ ] m i n min min m a x max ma x d f s dfs df s O ( n ) O(n) O ( n ) O ( l o g n ) O(logn) O ( l o g n ) t r e e [ ] tree[] t ree [ ] 记录区间最值  而不是区间和。以 t r e e [ ] tree[] t ree [ ] 699. 掉落的方块  一题,详细做法请参考 题解  。其中求区间最大值的代码如下,可以看到,与 t r e e [ ] tree[] t ree [ ] s u m sum s u m 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  int  max (int  l, int  r) {     return  max(l, r, 0 , n - 1 , 1 ); } private  int  max (int  l, int  r, int  s, int  t, int  i) {     if (l <= s && t <= r) return  tree[i];      int  c  =  s + (t - s) / 2 , lmax = 0 , rmax = 0 ;     if (lazy[i] != 0 ) pushDown(s, c, t, i);     if (l <= c) lmax = max(l, r, s, c, i * 2 );     if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );     return  Math.max(lmax, rmax); } 
另外要注意由于 t r e e [ ] tree[] t ree [ ] p u s h U p pushUp p u s h U p 
1 2 3 4 private  void  pushUp (int  i) {    tree[i] = Math.max(tree[i * 2 ], tree[i * 2  + 1 ]); } 
对于后续其他版本的线段树,只需将 t r e e [ ] tree[] t ree [ ] O ( l o g n ) O(logn) O ( l o g n ) O ( l o g n ) O(logn) O ( l o g n ) t r e e [ ] tree[] t ree [ ] t r e e [ ] tree[] t ree [ ] s u m sum s u m s u m sum s u m O ( n ) O(n) O ( n ) 
我们看到,无论 t r e e [ ] tree[] t ree [ ] if(s == t) return tree[i]; ,当需要包含于所求区间的当前区间信息时,基准情形为 if(l <= s && t <= r) return tree[i]; 。
在下面的「类的实现代码」中,只展示「不维护 n u m s nums n u m s O ( l o g n ) O(logn) O ( l o g n ) 
若要求线段树以 O ( l o g n ) O(logn) O ( l o g n ) t r e e S u m [ ] , t r e e M a x [ ] , t r e e M i n [ ] treeSum[],treeMax[],treeMin[] t ree S u m [ ] , t ree M a x [ ] , t ree M in [ ] 
类的实现代码 
将前述方法的实现代码组合起来即可 (实时维护 n u m s nums n u m s n u m s nums n u m s 
不过,我们在一开始说过线段树能很好地支持「区间修改」操作,为什么还没提供相关方法就在这里给出类的代码呢?这是因为,与树状数组 (RUPQ/RURQ BIT) 区间修改时只需要在 a d d add a dd O ( l o g n ) O(logn) O ( l o g n ) 线段树修改一段区间自上而下涉及到一个或多个子树空间内所有结点的修改  。例如若修改整个 n u m s nums n u m s d f s dfs df s 所有结点  ,时间复杂度为 O ( n ) O(n) O ( n ) O ( n ) O(n) O ( n ) n n n 4 n 4n 4 n n u m s nums n u m s 
为了解决这个问题,我们不是直接 d f s dfs df s 「懒惰标记」  的技巧,使子树中的修改操作 延迟  到后续修改和查询操作中,此技巧使得区间修改的时间复杂度仍为 O ( l o g n ) O(logn) O ( l o g n ) 
为了更稳固地学习后续内容,建议读者先基于目前为止讲解的内容尝试解决 307. 区域和检索 - 数组可修改  ,代码可参考「实战应用」一节给出的题解。
实时维护nums的版本 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 class  SegmentTreeBasic1  {    int [] nums, tree;     int  n;     public  SegmentTreeBasic1 (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .tree = new  int [4  * n];         build(0 , n - 1 , 1 );     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {          add(i, x - nums[i], 0 , n - 1 , 1 );         nums[i] = x;      }     public  int  query (int  i) {          return  nums[i];     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) add(idx, x, s, c, i * 2 );         else  add(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i];          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (l <= c) sum += sum(l, r, s, c, i * 2 );          if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );          return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (l <= c) lmin = min(l, r, s, c, i * 2 );         if (r > c) rmin = min(l, r, c + 1 , t, i * 2  + 1 );         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (l <= c) lmax = max(l, r, s, c, i * 2 );         if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );         return  Math.max(lmax, rmax);     }          private  void  build (int  s, int  t, int  i) {         if (s == t) {              tree[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  void  pushUp (int  i) {         tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ];     } } 
不维护nums的版本 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 class  SegmentTreeBasic2  {    int [] nums, tree;     int  n;     public  SegmentTreeBasic2 (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .tree = new  int [4  * n];         build(0 , n - 1 , 1 );     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {         update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 ;         if (idx <= c) return  query(idx, s, c, i * 2 );         else  return  query(idx, c + 1 , t, i * 2  + 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) add(idx, x, s, c, i * 2 );         else  add(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] = x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) update(idx, x, s, c, i * 2 );         else  update(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i];          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (l <= c) sum += sum(l, r, s, c, i * 2 );          if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );          return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (l <= c) lmin = min(l, r, s, c, i * 2 );         if (r > c) rmin = min(l, r, c + 1 , t, i * 2  + 1 );         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (l <= c) lmax = max(l, r, s, c, i * 2 );         if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );         return  Math.max(lmax, rmax);     }          private  void  build (int  s, int  t, int  i) {         if (s == t) {              tree[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  void  pushUp (int  i) {         tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ];     } } 
O(logn)时间求区间最大值版本 
以下是不维护 n u m s nums n u m s O ( l o g n ) O(logn) O ( l o g n ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 class  SegmentTreeBasicRangeMax  {    int [] nums, tree;     int  n;     public  SegmentTreeBasicRangeMax (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .tree = new  int [4  * n];         build(0 , n - 1 , 1 );     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {         update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 ;         if (idx <= c) return  query(idx, s, c, i * 2 );         else  return  query(idx, c + 1 , t, i * 2  + 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) add(idx, x, s, c, i * 2 );         else  add(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] = x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) update(idx, x, s, c, i * 2 );         else  update(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (l <= c) sum += sum(l, r, s, c, i * 2 );          if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );          return  sum;     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i];         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (l <= c) lmax = max(l, r, s, c, i * 2 );         if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );         return  Math.max(lmax, rmax);     }          private  void  build (int  s, int  t, int  i) {         if (s == t) {              tree[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         pushUp(i);     }          private  void  pushUp (int  i) {         tree[i] = Math.max(tree[i * 2 ], tree[i * 2  + 1 ]);     } } 
区间查询扩展版本 
以 O ( l o g n ) O(logn) O ( l o g n ) 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 class  SegmentTreeBasic  {    int [] nums, treeSum, treeMin,treeMax;     int  n;     public  SegmentTreeBasic (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .treeSum = new  int [4  * n];         this .treeMin = new  int [4  * n];         this .treeMax = new  int [4  * n];         build(0 , n - 1 , 1 );     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {         update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  treeSum[i];         int  c  =  s + (t - s) / 2 ;         if (idx <= c) return  query(idx, s, c, i * 2 );         else  return  query(idx, c + 1 , t, i * 2  + 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             treeSum[i] += x;              treeMin[i] += x;              treeMax[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) add(idx, x, s, c, i * 2 );         else  add(idx, x, c + 1 , t, i * 2  + 1 );         pushUpSum(i);         pushUpMin(i);         pushUpMax(i);     }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             treeSum[i] = x;              treeMin[i] = x;              treeMax[i] = x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (idx <= c) update(idx, x, s, c, i * 2 );         else  update(idx, x, c + 1 , t, i * 2  + 1 );         pushUpSum(i);         pushUpMin(i);         pushUpMax(i);     }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  treeSum[i];          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (l <= c) sum += sum(l, r, s, c, i * 2 );          if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );          return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  treeMin[i];         int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (l <= c) lmin = min(l, r, s, c, i * 2 );         if (r > c) rmin = min(l, r, c + 1 , t, i * 2  + 1 );         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  treeMax[i];         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (l <= c) lmax = max(l, r, s, c, i * 2 );         if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );         return  Math.max(lmax, rmax);     }          private  void  build (int  s, int  t, int  i) {         if (s == t) {              treeSum[i] = nums[s];             treeMin[i] = nums[s];             treeMax[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         pushUpSum(i);         pushUpMin(i);         pushUpMax(i);     }          private  void  pushUpSum (int  i) {         treeSum[i] = treeSum[i * 2 ] + treeSum[i * 2  + 1 ];     }          private  void  pushUpMin (int  i) {         treeMin[i] = Math.min(treeMin[i * 2 ], treeMin[i * 2  + 1 ]);     }          private  void  pushUpMax (int  i) {         treeMax[i] = Math.max(treeMax[i * 2 ], treeMax[i * 2  + 1 ]);     } } 
懒惰标记 
回顾一下前述 s u m sum s u m [ l , r ] [l,r] [ l , r ] [ l , r ] [l,r] [ l , r ] [ s , t ] [s,t] [ s , t ] l <= s && t <= r ) 时,我们直接累积该区间的区间和。可见,区间查询时,只要当前区间完全处于查询区间之内,我们就不需要知道此区间以下的区间的信息。因此对于区间查询 (求和) 操作,时间复杂度是 O ( l o g n ) O(logn) O ( l o g n ) 包含于修改区间之内的区间  时,是否可以 只修改代表该区间的区间和  ,而不必继续深入修改它的子区间的区间和呢?因为若修改区间下的子区间 (子树空间) 都要修改的话,相当于树的遍历,时间复杂度为 O ( n ) O(n) O ( n ) O ( l o g n ) O(logn) O ( l o g n ) 
因为递归方法总是同时传入结点下标 i i i [ s , t ] [s,t] [ s , t ] if(l <= s && t <= r) 来判断当前结点代表的区间是否完全包含于 [ l , r ] [l,r] [ l , r ] t r e e [ i ] tree[i] t ree [ i ] [ s , t ] [s,t] [ s , t ] [ l , r ] [l,r] [ l , r ] d f s dfs df s [ s , t ] [s,t] [ s , t ] t r e e [ i ] tree[i] t ree [ i ] t r e e [ i ] tree[i] t ree [ i ] [ l , r ] [l,r] [ l , r ] [ s , t ] [s,t] [ s , t ] t r e e [ i ] tree[i] t ree [ i ] [ s , t ] [s,t] [ s , t ] 
解决办法是在前一次修改 [ s , t ] [s,t] [ s , t ] t r e e [ i ] tree[i] t ree [ i ] t r e e [ ] tree[] t ree [ ] l a z y [ ] lazy[] l a zy [ ] l a z y [ i ] lazy[i] l a zy [ i ] t r e e [ i ] tree[i] t ree [ i ] 将这个修改传递给子区间  。我们将这个动作称为 「延迟修改」  ,将这个标记形象地称作 「懒惰标记」  。不好理解?没关系,我们马上结合示意图与代码来跟踪相关操作。
区间修改 (增量式) 
我们先分析 为区间内所有元素增加同一值的「增量式区间修改」  操作。
如下图,执行 add(6, 11, 2) 为区间 [ 6 , 11 ] [6,11] [ 6 , 11 ] t r e e [ 6 ] tree[6] t ree [ 6 ] t r e e [ 11 ] tree[11] t ree [ 11 ] tree[6] += (11 - 8 + 1) * 2 和 tree[11] += (7 - 6 + 1) * 2 。同时,增量 2 也会被 l a z y [ 6 ] lazy[6] l a zy [ 6 ] l a z y [ 11 ] lazy[11] l a zy [ 11 ] 标记可能还记录了前面的修改,而修改是增量式的,因此需累加  ,即 lazy[6] += 2 及 lazy[11] += 2。
接着,我们查询 [ 6 , 9 ] [6,9] [ 6 , 9 ] s u m ( 6 , 9 ) sum(6,9) s u m ( 6 , 9 ) t r e e [ 6 ] tree[6] t ree [ 6 ] l a z y [ 6 ] lazy[6] l a zy [ 6 ] t r e e [ 12 ] tree[12] t ree [ 12 ] t r e e [ 13 ] tree[13] t ree [ 13 ] t r e e [ 11 ] tree[11] t ree [ 11 ] l a z y [ 11 ] lazy[11] l a zy [ 11 ] t r e e [ 22 ] tree[22] t ree [ 22 ] t r e e [ 23 ] tree[23] t ree [ 23 ] 「推送」  指的是通过 p u s h D o w n pushDown p u s h Do w n 当前区间以及它的两个子结点区间的区间和以及懒标记的更新  。
现在我们很容易理解下面的实现代码。private void add 方法中,当递进到所代表的区间完全包含于 [ l , r ] [l,r] [ l , r ] t r e e [ i ] tree[i] t ree [ i ] [ s , t ] [s,t] [ s , t ] tree[i] += (t - s + 1) * x 语句更新 t r e e [ i ] tree[i] t ree [ i ] if(s != t) lazy[i] += x  后返回。懒标记更新前的判断使得 叶子结点不会被标记  ,因为 叶子结点之下不再有需要推送标记的结点  。当然也可以不用判断,因为无论是查询还是修改,到达叶子结点时一定会进入方法开始的 i f if i f r e t u r n return re t u r n 
if(lazy[i] != 0) pushDown(s, c, t, i) 表示在递归进入左右子结点前,检查当前 t r e e [ i ] tree[i] t ree [ i ] lazy[i] != 0 说明当前 t r e e [ i ] tree[i] t ree [ i ] 尚未推送的修改  ,需要在这个时候推送到下一层,否则递归进入左右子结点时,左右子结点的区间和是旧的。
p u s h D o w n pushDown p u s h Do w n s , c , t , i s,c,t,i s , c , t , i t r e e [ i ] tree[i] t ree [ i ] t r e e [ 2 ∗ i ] tree[2*i] t ree [ 2 ∗ i ] t r e e [ 2 ∗ i + 1 ] tree[2*i+1] t ree [ 2 ∗ i + 1 ] t r e e [ i ] tree[i] t ree [ i ] lazy[i] = 0 ,否则下次修改或查询再经过 t r e e [ i ] tree[i] t ree [ i ] 
a d d add a dd p u s h U p pushUp p u s h U p 「后序」  动作,回溯过程中自底向上更新递进路径上的 t r e e [ i ] tree[i] t ree [ i ] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public  void  add (int  l, int  r, int  x) {     add(l, r, x, 0 , n - 1 , 1 ); } private  void  add (int  l, int  r, int  x, int  s, int  t, int  i) {     if (l <= s && t <= r){          tree[i] += (t - s + 1 ) * x;          if (s != t) lazy[i] += x;          return ;     }     int  c  =  s + (t - s) / 2 ;     if (lazy[i] != 0 ) pushDown(s, c, t, i);      if (l <= c) add(l, r, x, s, c, i * 2 );     if (r > c) add(l, r, x, c + 1 , t, i * 2  + 1 );     pushUp(i);  } private  void  pushDown (int  s, int  c, int  t, int  i) {     tree[i * 2 ] += (c - s + 1 ) * lazy[i];      lazy[i * 2 ] += lazy[i];      tree[i * 2  + 1 ] += (t - c) * lazy[i];     lazy[i * 2  + 1 ] += lazy[i];     lazy[i] = 0 ;  } private  void  pushUp (int  i) {     tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ]; } 
区间修改 (覆盖式) 
接着分析 将区间内所有元素修改为同一值的「覆盖式区间修改」  操作。与单点覆盖式修改方法可以通过调用单点增量式修改方法来实现不同,由于 涉及多个值的修改  ,区间覆盖式修改不能通过调用区间增量式方法来实现。且覆盖式修改可能将 n u m s nums n u m s lazy[i] != 0 来作为推送标记的标志。我们可以创建一个新的 boolean updated[] 数组来记录结点 i i i updated[i] == true 说明结点 i i i 覆盖式修改未推送  。需注意的是, p u s h D o w n pushDown p u s h Do w n if(updated[i]) 代替了 if(lazy[i] != 0) ,以及将「增量赋值」的 += 改为「覆盖赋值」 = ,以及相应的 u p d a t e d [ i ] updated[i] u p d a t e d [ i ] 
如果我们能确定覆盖式区间修改不会将元素值改为 0 的话,那 u p d a t e d [ ] updated[] u p d a t e d [ ] l a z y [ i ] lazy[i] l a zy [ i ] += 和 = 的区别。例如 699. 掉落的方块  一题,需要实现覆盖式区间修改,但题目保证了修改值不会是 0 ,实现代码就不需要另外使用 boolean updated[] ,具体请看「实战应用」中该题 题解 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public  void  update (int  l, int  r, int  x) {     update(l, r, x, 0 , n - 1 , 1 ); } private  void  update (int  l, int  r, int  x, int  s, int  t, int  i) {     if (l <= s && t <= r){          tree[i] = (t - s + 1 ) * x;          if (s != t) {              lazy[i] = x;              updated[i] = true ;          }         return ;     }     int  c  =  s + (t - s) / 2 ;     if (updated[i]) pushDown(s, c, t, i);      if (l <= c) update(l, r, x, s, c, i * 2 );     if (r > c) update(l, r, x, c + 1 , t, i * 2  + 1 );     pushUp(i);  } private  void  pushDown (int  s, int  c, int  t, int  i) {     tree[i * 2 ] = (c - s + 1 ) * lazy[i];      lazy[i * 2 ] = lazy[i];      updated[i * 2 ] = true ;     tree[i * 2  + 1 ] = (t - c) * lazy[i];     lazy[i * 2  + 1 ] = lazy[i];     updated[i * 2  + 1 ] = true ;     lazy[i] = 0 ;      updated[i] = false ;  } 
类的实现代码 
增量式区间修改版 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 class  SegmentTreeAdd {    int [] nums, tree, lazy;     int  n;     public  SegmentTreeAdd (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .tree = new  int [4  * n];         this .lazy = new  int [4  * n];         build(0 , n - 1 , 1 );     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {          update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  void  add (int  l, int  r, int  x) {          add(l, r, x, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (lazy[i] != 0 ) pushDown(s, c, t, i);          if (idx <= c) add(idx, x, s, c, i * 2 );         else  add(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);      }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] = x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (lazy[i] != 0 ) pushDown(s, c, t, i);          if (idx <= c) update(idx, x, s, c, i * 2 );         else  update(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);       }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 ;         if (lazy[i] != 0 ) pushDown(s, c, t, i);          if (idx <= c) return  query(idx, s, c, i * 2 );         else  return  query(idx, c + 1 , t, i * 2  + 1 );     }          private  void  add (int  l, int  r, int  x, int  s, int  t, int  i) {         if (l <= s && t <= r){              tree[i] += (t - s + 1 ) * x;              if (s != t) lazy[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (lazy[i] != 0 ) pushDown(s, c, t, i);          if (l <= c) add(l, r, x, s, c, i * 2 );         if (r > c) add(l, r, x, c + 1 , t, i * 2  + 1 );         pushUp(i);      }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i];          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (lazy[i] != 0 ) pushDown(s, c, t, i);          if (l <= c) sum += sum(l, r, s, c, i * 2 );         if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );         return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (lazy[i] != 0 ) pushDown(s, c, t, i);          if (l <= c) lmin = min(l, r, s, c, i * 2 );         if (r > c) rmin = min(l, r, c + 1 , t, i * 2  + 1 );         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (lazy[i] != 0 ) pushDown(s, c, t, i);         if (l <= c) lmax = max(l, r, s, c, i * 2 );         if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );         return  Math.max(lmax, rmax);     }          private  void  build (int  s, int  t, int  i) {         if (s == t) {              tree[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         pushUp(i);       }          private  void  pushUp (int  i) {         tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ];     }          private  void  pushDown (int  s, int  c, int  t, int  i) {         tree[i * 2 ] += (c - s + 1 ) * lazy[i];          lazy[i * 2 ] += lazy[i];          tree[i * 2  + 1 ] += (t - c) * lazy[i];         lazy[i * 2  + 1 ] += lazy[i];         lazy[i] = 0 ;      } } 
覆盖式区间修改版 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 class  SegmentTreeUpdate {    int [] nums, tree, lazy;     boolean [] updated;     int  n;     public  SegmentTreeUpdate (int [] nums) {         this .nums = nums;         this .n = nums.length;         this .tree = new  int [4  * n];         this .lazy = new  int [4  * n];         this .updated = new  boolean [4  * n];         build(0 , n - 1 , 1 );     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {          update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  void  update (int  l, int  r, int  x) {          update(l, r, x, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] += x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (updated[i]) pushDown(s, c, t, i);          if (idx <= c) add(idx, x, s, c, i * 2 );         else  add(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);      }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i] = x;              return ;         }         int  c  =  s + (t - s) / 2 ;         if (updated[i]) pushDown(s, c, t, i);          if (idx <= c) update(idx, x, s, c, i * 2 );         else  update(idx, x, c + 1 , t, i * 2  + 1 );         pushUp(i);       }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 ;         if (updated[i]) pushDown(s, c, t, i);          if (idx <= c) return  query(idx, s, c, i * 2 );         else  return  query(idx, c + 1 , t, i * 2  + 1 );     }          private  void  update (int  l, int  r, int  x, int  s, int  t, int  i) {         if (l <= s && t <= r){              tree[i] = (t - s + 1 ) * x;              if (s != t) {                  lazy[i] = x;                  updated[i] = true ;              }             return ;         }         int  c  =  s + (t - s) / 2 ;         if (updated[i]) pushDown(s, c, t, i);          if (l <= c) update(l, r, x, s, c, i * 2 );         if (r > c) update(l, r, x, c + 1 , t, i * 2  + 1 );         pushUp(i);      }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i];          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (updated[i]) pushDown(s, c, t, i);          if (l <= c) sum += sum(l, r, s, c, i * 2 );         if (r > c) sum += sum(l, r, c + 1 , t, i * 2  + 1 );         return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (updated[i]) pushDown(s, c, t, i);          if (l <= c) lmin = min(l, r, s, c, i * 2 );         if (r > c) rmin = min(l, r, c + 1 , t, i * 2  + 1 );         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i];         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (updated[i]) pushDown(s, c, t, i);         if (l <= c) lmax = max(l, r, s, c, i * 2 );         if (r > c) rmax = max(l, r, c + 1 , t, i * 2  + 1 );         return  Math.max(lmax, rmax);     }          private  void  build (int  s, int  t, int  i) {         if (s == t) {              tree[i] = nums[s];             return ;         }         int  c  =  s + (t - s) / 2 ;         build(s, c, i * 2 );         build(c + 1 , t, i * 2  + 1 );         pushUp(i);       }          private  void  pushUp (int  i) {         tree[i] = tree[i * 2 ] + tree[i * 2  + 1 ];     }          private  void  pushDown (int  s, int  c, int  t, int  i) {         tree[i * 2 ] = (c - s + 1 ) * lazy[i];          lazy[i * 2 ] = lazy[i];          updated[i * 2 ] = true ;         tree[i * 2  + 1 ] = (t - c) * lazy[i];         lazy[i * 2  + 1 ] = lazy[i];         updated[i * 2  + 1 ] = true ;         lazy[i] = 0 ;          updated[i] = false ;      } } 
小结 
学会了带懒惰标记的线段树后,对于「区间问题」,我们终于掌握了一个比「树状数组」更强大的工具。对于大小为 n n n n u m s nums n u m s O ( l o g n ) O(logn) O ( l o g n ) 「单点修改」、「单点查询」、「区间修改」和「区间查询」  。其中,「区间修改」支持增量式或覆盖式,「区间查询」除了能求区间和,也可以求区间最值,但是要注意 t r e e [ ] tree[] t ree [ ] 
实际上线段树经过一些调整,能够实现更多的区间运算。
离散化 
当我们满怀信心带着已掌握的工具试图求解线段树题目时,我们马上会遇到一个难题 ── 空间爆炸 !仍以 699. 掉落的方块  为例,题意不难理解,实际上就是要实现「覆盖式区间修改」和「区间最大值查询」操作。根据题目的数据范围提示,求解区间从 1 到 最右边方块的右界  为止,这个值是 n = 1 0 8 + 1 0 6 n=10^8+10^6 n = 1 0 8 + 1 0 6 t r e e [ ] tree[] t ree [ ] 4 ∗ n = 4 ∗ ( 1 0 8 + 1 0 6 ) 4*n=4*(10^8+10^6) 4 ∗ n = 4 ∗ ( 1 0 8 + 1 0 6 ) M L E MLE M L E 
解决该问题的办法我们实际上已在「树状数组 」中介绍过,那就是在不影响求解的情况下,先将求解区间离散化。以 699 题为例,方块 [ l , l + h ] [l,l+h] [ l , l + h ] [ l , l + h − 1 ] [l,l+h-1] [ l , l + h − 1 ] h e i g h t height h e i g h t h + h e i g h t h + height h + h e i g h t 题解  。
离散化主要分为紧离散和松离散,它们都基于排序,二者的区别已在「树状数组 」中讲解过。
紧离散 
借助 s e t set se t 
1 2 3 4 5 6 7 8 9 10 11 private  Map<Integer, Integer> discrete (int [] nums) {     Map<Integer, Integer> map = new  HashMap <>();     Set<Integer> set = new  HashSet <>();     for (int  num : nums) set.add(num);     List<Integer> list = new  ArrayList <>(set);     Collections.sort(list);     int  idx  =  0 ;     for (int  num : list) map.put(num, ++idx);     return  map; } 
松离散 
不借助 s e t set se t 
1 2 3 4 5 6 7 8 9 10 private  void  discrete (int [] nums) {     int  n  =  nums.length;     int [] tmp = new  int [n];     System.arraycopy(nums, 0 , tmp, 0 , n);     Arrays.sort(tmp);     for  (int  i  =  0 ; i < n; ++i) {         nums[i] = Arrays.binarySearch(tmp, nums[i]) + 1 ;     } } 
举例而言,对于 n u m s = { 2 , 4 , 4 , 6 } nums=\{2,4,4,6\} n u m s = { 2 , 4 , 4 , 6 } n u m s = { 1 , 2 , 2 , 4 } nums=\{1,2,2,4\} n u m s = { 1 , 2 , 2 , 4 } n u m s nums n u m s m a p = { ( 2 , 1 ) , ( 4 , 2 ) , ( 6 , 3 ) } map=\{(2,1),(4,2),(6,3)\} ma p = {( 2 , 1 ) , ( 4 , 2 ) , ( 6 , 3 )} k e y key k ey n u m s nums n u m s v a l u e value v a l u e v a l u e value v a l u e 
两种方式离散化后虽然有效数字不同,取值范围也不同,但通常都是有效的。松离散无需哈希计算,通常速度更快,但有的题目可能更适合返回 m a p map ma p 
动态开点 
现在,有了「离散化」这一工具,我们重新出发。在用「离散化」锤掉 699 题之后,继续尝试解决 715. Range 模块  一题。按如下理解题意,不难看出这也是一道非常典型的线段树题目。可用支持覆盖式「区间修改」和「区间查询 (求和)」的线段树实现。
a d d R a n g e addRange a dd R an g e [ l e f t , r i g h t − 1 ] [left,right-1] [ l e f t , r i g h t − 1 ] q u e r y R a n g e queryRange q u ery R an g e [ l e f t , r i g h t − 1 ] [left,right-1] [ l e f t , r i g h t − 1 ] s u m = r i g h t − l e f t sum = right-left s u m = r i g h t − l e f t t r u e true t r u e f a l s e false f a l se r e m o v e R a n g e removeRange re m o v e R an g e [ l e f t , r i g h t − 1 ] [left,right-1] [ l e f t , r i g h t − 1 ]  
根据题目给出的取值范围,我们看到「区间」范围为 [ 1 , 1 0 9 ] [1,10^9] [ 1 , 1 0 9 ] t r e e [ ] tree[] t ree [ ] 4 ∗ 1 0 9 4*10^9 4 ∗ 1 0 9 M L E MLE M L E 强制在线 
※ l o g ( 4 ∗ 1 0 9 ) > 31 , 2 31 = 2 G b i t log(4*10^9) > 31 ,2^{31} = 2Gbit l o g ( 4 ∗ 1 0 9 ) > 31 , 2 31 = 2 G bi t 4 ∗ 1 0 9 4*10^9 4 ∗ 1 0 9 2 G b 2Gb 2 G b 
现在请看向本小节的标题 —— 「动态开点」  ,如果你此前有瞥过一些线段树的文章,你应该没少看到过这个词。总之你大概知道既然不能在程序开始时就完成初始「线段树」的构建的话,那就在区间查询或区间修改时,根据届时传入的区间信息来「动态地」创建结点,那么如何做到呢?
为了引出如何动态创建结点,我们先回顾「堆式线段树」的操作。每个结点代表一个确定的区间,当我们需要查询或修改区间 [ l , r ] [l,r] [ l , r ] [ l , r ] [l,r] [ l , r ] t r e e [ i ] tree[i] t ree [ i ] l a z y / u p d a t e d lazy/updated l a zy / u p d a t e d [ s , t ] [s,t] [ s , t ] [ l , r ] [l,r] [ l , r ] i i i t r e e [ i ] tree[i] t ree [ i ] s , t , i s,t,i s , t , i 
方法执行过程中保证结点值 (指 t r e e [ i ] tree[i] t ree [ i ] [ s , t ] [s,t] [ s , t ]  。
 
堆式线段树通过 i , 2 ∗ i , 2 ∗ i + 1 i, 2*i, 2*i+1 i , 2 ∗ i , 2 ∗ i + 1 
一个直接的想法是不再由 int[] tree 来存储结点值信息,而是以 Node[] tree 来维护结点信息,N o d e Node N o d e v a l val v a l l I d x , r I d x lIdx, rIdx l I d x , r I d x t r e e [ t r e e [ i ] . l I d x ] tree[tree[i].lIdx] t ree [ t ree [ i ] . l I d x ] t r e e [ t r e e [ i ] . r I d x ] tree[tree[i].rIdx] t ree [ t ree [ i ] . r I d x ] t r e e [ t r e e [ i ] . l I d x ] . v a l tree[tree[i].lIdx].val t ree [ t ree [ i ] . l I d x ] . v a l t r e e [ t r e e [ i ] . r I d x ] . v a l tree[tree[i].rIdx].val t ree [ t ree [ i ] . r I d x ] . v a l [ s , t ] [s,t] [ s , t ] 
到这里,我们实际上已经给出了「动态开点线段树」的第一种实现方式 —— 结点数组法。
结点数组法 
我们直接给出如下包含了构造器和区间修改 (增量式) 的代码,结合后图分析 「结点数组法动态线段树」  是如何「动态开点」的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class  DynamicSegmentTreeArrayAdd {     private  class  Node {         int  lIdx, rIdx, lazy, val;     }     Node[] tree;     int  n, count;     public  DynamicSegmentTreeArrayAdd (int  n, int  m) {         this .n = n;         this .count = 1 ;         this .tree = new  Node [m];     }     public  void  add (int  l, int  r, int  x) {          add(l, r, x, 0 , n - 1 , 1 );     }              private  void  add (int  l, int  r, int  x, int  s, int  t, int  i) {         if (l <= s && t <= r){              tree[i].val += (t - s + 1 ) * x;              if (s != t) tree[i].lazy += x;              return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (l <= c) add(l, r, x, s, c, tree[i].lIdx);         if (r > c) add(l, r, x, c + 1 , t, tree[i].rIdx);         pushUp(i);      }          private  void  addNode (int  i) {         if (tree[i] == null ) tree[i] = new  Node ();          if (tree[i].lIdx == 0 ) {              tree[i].lIdx = ++count;              tree[tree[i].lIdx] = new  Node ();          }         if (tree[i].rIdx == 0 ) {              tree[i].rIdx = ++count;             tree[tree[i].rIdx] = new  Node ();         }     } } 
首先看构造器。n n n [ 0 , n − 1 ] [0,n-1] [ 0 , n − 1 ] 整个区间的范围是必须知道的  ,因为我们要从输入区间开始不断划分左右区间,因此要传入 n n n m m m t r e e tree t ree t r e e [ ] tree[] t ree [ ] N o d e Node N o d e 4 ∗ n 4*n 4 ∗ n n n n 「估计」  ,估计方法后述。
接着看区间修改的驱动方法和具体的区间修改方法,它们与我们前面给出的堆式线段树的写法几乎相同,区别只在于多了一行 addNode(i) 。 a d d add a dd a d d add a dd 尚未创建左右子结点,甚至当前结点也可能尚未创建  ,因此必须执行 addNode(i) ,根据方法中的判断结果来决定是否要创建当前结点 t r e e [ i ] tree[i] t ree [ i ] t r e e [ t r e e [ i ] . l I d x ] tree[tree[i].lIdx] t ree [ t ree [ i ] . l I d x ] t r e e [ t r e e [ i ] . r I d x ] tree[tree[i].rIdx] t ree [ t ree [ i ] . r I d x ] tree[tree[i].lIdx] == null 而是通过 tree[i].lIdx == 0 来判断即可。
同样地,对于其他查询或修改方法,与堆式线段树的实现的区别都只是是在方法中多了 addNode(i) 这一行「开点」语句,如此就实现了 只在查询或修改时按需动态地创建结点  这一目标。 完整的类实现我们放在稍后的「类的实现代码」中。如下 ①~⑨ 是在区间为 [ 0 , 14 ] [0,14] [ 0 , 14 ] [ 1 , 3 ] [1,3] [ 1 , 3 ] 
预估结点数 
t r e e [ ] tree[] t ree [ ] m m m h h h n n n 不太大 ,我们就能估计经过所有操作后创建的总的结点数,即线段树的大小 m m m 
整棵线段树的结点数不会超过 4 ∗ n 4*n 4 ∗ n  
每次查询从根结点往下,在假设查询或修改的区间 只有一个值  ,则每次操作从根结点到表示该值的叶子结点的路径上,每一层只会经过一个区间,开两个点。于是开点次数为树高 h = ⌈ l o g 2 ( 4 ∗ n ) ⌉ − 1 = l o g 2 ( 4 ∗ n ) h = \lceil log_{2}(4*n) \rceil -1=log_{2}(4*n) h = ⌈ l o g 2  ( 4 ∗ n )⌉ − 1 = l o g 2  ( 4 ∗ n ) 2 ∗ h 2*h 2 ∗ h 6 ∗ h 6*h 6 ∗ h  
假设有 k k k m = 6 ∗ k ∗ l o g 2 ( 4 ∗ n ) m=6*k*log_{2}(4*n) m = 6 ∗ k ∗ l o g 2  ( 4 ∗ n ) 6 ∗ k ∗ 2 6*k*2 6 ∗ k ∗ 2 m = 6 ∗ k ∗ l o g n m = 6*k*logn m = 6 ∗ k ∗ l o g n  
 
现在,在参考「类的实现代码」中「结点数组法」版本的代码以及 题解  的基础上,应当很容易写出「结点数组法动态线段树」解决  715. Range 模块  。
应用结点数组法动态线段树解决 715题 时,我们会感到  m m m m m m A r r a y L i s t ArrayList A rr a y L i s t t r e e [ ] tree[] t ree [ ] N o d e Node N o d e O ( l o g n ) O(logn) O ( l o g n ) 
避免数组大小预估的思考引出下面的 「结点指针 (引用)」法动态线段树」  。
结点指针 (引用) 法 
避免数组大小预估的办法很简单。我们不再通过数组来保存结点信息,而是在 N o d e Node N o d e N o d e Node N o d e l C h i l d lChild lC hi l d r C h i l d rChild r C hi l d [ l , r ] [l,r] [ l , r ] c u r cur c u r c u r . l C h i l d cur.lChild c u r . lC hi l d c u r . r C h i l d cur.rChild c u r . r C hi l d 
结点指针法实现的动态线段树是简单的。我们直接给出如下包含了构造器和区间修改 (增量式) 的代码,对比「结点数组」法的代码,主要区别为:
指针法的查询或修改方法,直接传入当前结点 Node cur 。 
指针法的线段树类维护根结点 r o o t root roo t  
动态开点方法 a d d N o d e addNode a dd N o d e  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class  DynamicSegmentTreePointerAdd {    private  class  Node {         int  lazy, val;         Node lChild, rChild;     }     int  n;     Node root;     public  DynamicSegmentTreePointerAdd (int  n) {         this .n = n;         this .root = new  Node ();     }     public  void  add (int  l, int  r, int  x) {          add(l, r, x, 0 , n - 1 , root);     }          private  void  add (int  idx, int  x, int  s, int  t, Node cur) {          if (s == t) {             cur.val += x;              return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (idx <= c) add(idx, x, s, c, cur.lChild);         else  add(idx, x, c + 1 , t, cur.rChild);         pushUp(cur);      }          private  void  addNode (Node node) {          if (node.lChild == null ) node.lChild = new  Node ();         if (node.rChild == null ) node.rChild = new  Node ();     } } 
完整实现请参考「类的实现代码」。
从前面一步步看到这里,我们明显感到动态开点的线段树并不复杂,相比静态线段树,实现上的区别不过是引入了一个 a d d N o d e addNode a dd N o d e 
现在,在参考「类的实现代码」中「结点指针 (引用) 法」版本的代码以及 题解  的基础上,应当很容易写出「结点指针 (引用) 法动态线段树」解决  715. Range 模块  。
类的实现代码 
结点数组法 
增量式区间修改版 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 class  DynamicSegmentTreeArrayAdd {    private  class  Node {         int  lIdx, rIdx, lazy, val;     }     Node[] tree;     int  n, count;     public  DynamicSegmentTreeArrayAdd (int  n, int  m) {         this .n = n;         this .count = 1 ;         this .tree = new  Node [m];     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {          update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  void  add (int  l, int  r, int  x) {          add(l, r, x, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i].val += x;              return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (idx <= c) add(idx, x, s, c, tree[i].lIdx);         else  add(idx, x, c + 1 , t, tree[i].rIdx);         pushUp(i);      }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i].val = x;              return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (idx <= c) update(idx, x, s, c, tree[i].lIdx);         else  update(idx, x, c + 1 , t, tree[i].rIdx);         pushUp(i);       }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  tree[i].val;         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (idx <= c) return  query(idx, s, c, tree[i].lIdx);         else  return  query(idx, c + 1 , t, tree[i].rIdx);     }          private  void  add (int  l, int  r, int  x, int  s, int  t, int  i) {         if (l <= s && t <= r){              tree[i].val += (t - s + 1 ) * x;              if (s != t) tree[i].lazy += x;              return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (l <= c) add(l, r, x, s, c, tree[i].lIdx);         if (r > c) add(l, r, x, c + 1 , t, tree[i].rIdx);         pushUp(i);      }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i].val;          addNode(i);          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (l <= c) sum += sum(l, r, s, c, tree[i].lIdx);         if (r > c) sum += sum(l, r, c + 1 , t, tree[i].rIdx);         return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i].val;          addNode(i);          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);          if (l <= c) lmin = min(l, r, s, c, tree[i].lIdx);         if (r > c) rmin = min(l, r, c + 1 , t, tree[i].rIdx);         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i].val;         addNode(i);          int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (tree[i].lazy != 0 ) pushDown(s, c, t, i);         if (l <= c) lmax = max(l, r, s, c, tree[i].lIdx);         if (r > c) rmax = max(l, r, c + 1 , t, tree[i].rIdx);         return  Math.max(lmax, rmax);     }          private  void  pushUp (int  i) {         Node  cur  =  tree[i], lChild = tree[cur.lIdx], rChild = tree[cur.rIdx];         cur.val = lChild.val + rChild.val;     }          private  void  pushDown (int  s, int  c, int  t, int  i) {         Node  cur  =  tree[i], lChild = tree[cur.lIdx], rChild = tree[cur.rIdx];         lChild.val += (c - s + 1 ) * cur.lazy;          lChild.lazy += cur.lazy;          rChild.val += (t - c) * cur.lazy;         rChild.lazy += cur.lazy;         cur.lazy = 0 ;     }          private  void  addNode (int  i) {         if (tree[i] == null ) tree[i] = new  Node ();          if (tree[i].lIdx == 0 ) {              tree[i].lIdx = ++count;              tree[tree[i].lIdx] = new  Node ();          }         if (tree[i].rIdx == 0 ) {              tree[i].rIdx = ++count;             tree[tree[i].rIdx] = new  Node ();         }     } } 
覆盖式区间修改版 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 class  DynamicSegmentTreeArrayUpdate {    private  class  Node {         int  lIdx, rIdx, lazy, val;         boolean  updated;     }     Node[] tree;     int  n, count;     public  DynamicSegmentTreeArrayUpdate (int  n, int  m) {         this .n = n;         this .count = 1 ;         this .tree = new  Node [m];     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , 1 );     }     public  void  update (int  i, int  x) {          update(i, x, 0 , n - 1 , 1 );     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , 1 );     }     public  void  update (int  l, int  r, int  x) {          update(l, r, x, 0 , n - 1 , 1 );     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , 1 );     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , 1 );     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , 1 );     }          private  void  add (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i].val += x;              return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].updated) pushDown(s, c, t, i);          if (idx <= c) add(idx, x, s, c, tree[i].lIdx);         else  add(idx, x, c + 1 , t, tree[i].rIdx);         pushUp(i);      }          private  void  update (int  idx, int  x, int  s, int  t, int  i) {         if (s == t) {             tree[i].val = x;              return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].updated) pushDown(s, c, t, i);          if (idx <= c) update(idx, x, s, c, tree[i].lIdx);         else  update(idx, x, c + 1 , t, tree[i].rIdx);         pushUp(i);       }          private  int  query (int  idx, int  s, int  t, int  i) {         if (s == t) return  tree[i].val;         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].updated) pushDown(s, c, t, i);          if (idx <= c) return  query(idx, s, c, tree[i].lIdx);         else  return  query(idx, c + 1 , t, tree[i].rIdx);     }          private  void  update (int  l, int  r, int  x, int  s, int  t, int  i) {         if (l <= s && t <= r){              tree[i].val = (t - s + 1 ) * x;              if (s != t) {                 tree[i].lazy = x;                  tree[i].updated = true ;              }             return ;         }         addNode(i);          int  c  =  s + (t - s) / 2 ;         if (tree[i].updated) pushDown(s, c, t, i);          if (l <= c) update(l, r, x, s, c, tree[i].lIdx);         if (r > c) update(l, r, x, c + 1 , t, tree[i].rIdx);         pushUp(i);      }          private  int  sum (int  l, int  r, int  s, int  t, int  i) {         if (l <= s && t <= r) return  tree[i].val;          addNode(i);          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (tree[i].updated) pushDown(s, c, t, i);          if (l <= c) sum += sum(l, r, s, c, tree[i].lIdx);         if (r > c) sum += sum(l, r, c + 1 , t, tree[i].rIdx);         return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i].val;          addNode(i);          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (tree[i].updated) pushDown(s, c, t, i);          if (l <= c) lmin = min(l, r, s, c, tree[i].lIdx);         if (r > c) rmin = min(l, r, c + 1 , t, tree[i].rIdx);         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, int  i) {         if (s == t) return  tree[i].val;         addNode(i);          int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (tree[i].updated) pushDown(s, c, t, i);          if (l <= c) lmax = max(l, r, s, c, tree[i].lIdx);         if (r > c) rmax = max(l, r, c + 1 , t, tree[i].rIdx);         return  Math.max(lmax, rmax);     }          private  void  pushUp (int  i) {         Node  cur  =  tree[i], lChild = tree[cur.lIdx], rChild = tree[cur.rIdx];         cur.val = lChild.val + rChild.val;     }          private  void  pushDown (int  s, int  c, int  t, int  i) {         Node  cur  =  tree[i], lChild = tree[cur.lIdx], rChild = tree[cur.rIdx];         lChild.val = (c - s + 1 ) * cur.lazy;          lChild.lazy = cur.lazy;          lChild.updated = true ;          rChild.val = (t - c) * cur.lazy;         rChild.lazy = cur.lazy;         rChild.updated = true ;         cur.lazy = 0 ;          cur.updated = false ;      }          private  void  addNode (int  i) {         if (tree[i] == null ) tree[i] = new  Node ();         if (tree[i].lIdx == 0 ) {              tree[i].lIdx = ++count;              tree[tree[i].lIdx] = new  Node ();          }         if (tree[i].rIdx == 0 ) {              tree[i].rIdx = ++count;             tree[tree[i].rIdx] = new  Node ();         }     } } 
结点指针 (引用) 法 
增量式区间修改版 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 class  DynamicSegmentTreePointerAdd {    private  class  Node {         int  lazy, val;         Node lChild, rChild;     }     int  n;     Node root;     public  DynamicSegmentTreePointerAdd (int  n) {         this .n = n;         this .root = new  Node ();     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , root);     }     public  void  update (int  i, int  x) {          update(i, x, 0 , n - 1 , root);     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , root);     }     public  void  add (int  l, int  r, int  x) {          add(l, r, x, 0 , n - 1 , root);     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , root);     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , root);     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , root);     }          private  void  add (int  idx, int  x, int  s, int  t, Node cur) {         if (s == t) {             cur.val += x;              return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (idx <= c) add(idx, x, s, c, cur.lChild);         else  add(idx, x, c + 1 , t, cur.rChild);         pushUp(cur);      }          private  void  update (int  idx, int  x, int  s, int  t, Node cur) {         if (s == t) {             cur.val = x;              return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (idx <= c) update(idx, x, s, c, cur.lChild);         else  update(idx, x, c + 1 , t, cur.rChild);         pushUp(cur);       }          private  int  query (int  i, int  s, int  t, Node cur) {         if (s == t) return  cur.val;         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (i <= c) return  query(i, s, c, cur.lChild);         else  return  query(i, c + 1 , t, cur.rChild);     }          private  void  add (int  l, int  r, int  x, int  s, int  t, Node cur) {         if (l <= s && t <= r){              cur.val += (t - s + 1 ) * x;              if (s != t) cur.lazy += x;              return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (l <= c) add(l, r, x, s, c, cur.lChild);         if (r > c) add(l, r, x, c + 1 , t, cur.rChild);         pushUp(cur);      }          private  int  sum (int  l, int  r, int  s, int  t, Node cur) {         if (l <= s && t <= r) return  cur.val;          addNode(cur);          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (l <= c) sum += sum(l, r, s, c, cur.lChild);         if (r > c) sum += sum(l, r, c + 1 , t, cur.rChild);         return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, Node cur) {         if (s == t) return  cur.val;          addNode(cur);          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);          if (l <= c) lmin = min(l, r, s, c, cur.lChild);         if (r > c) rmin = min(l, r, c + 1 , t, cur.rChild);         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, Node cur) {         if (s == t) return  cur.val;         addNode(cur);         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (cur.lazy != 0 ) pushDown(s, c, t, cur);         if (l <= c) lmax = max(l, r, s, c, cur.lChild);         if (r > c) rmax = max(l, r, c + 1 , t, cur.rChild);         return  Math.max(lmax, rmax);     }          private  void  pushUp (Node cur) {         Node  lChild  =  cur.lChild, rChild = cur.rChild;         cur.val = lChild.val + rChild.val;     }          private  void  pushDown (int  s, int  c, int  t, Node cur) {         Node  lChild  =  cur.lChild, rChild = cur.rChild;         lChild.val += (c - s + 1 ) * cur.lazy;          lChild.lazy += cur.lazy;          rChild.val += (t - c) * cur.lazy;         rChild.lazy += cur.lazy;         cur.lazy = 0 ;      }          private  void  addNode (Node node) {         if (node.lChild == null ) node.lChild = new  Node ();         if (node.rChild == null ) node.rChild = new  Node ();     } } 
覆盖式区间修改版 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 class  DynamicSegmentTreePointerUpdate {    private  class  Node {         int  lazy, val;         boolean  updated;         Node lChild, rChild;     }     int  n;     Node root;     public  DynamicSegmentTreePointerUpdate (int  n) {         this .n = n;         this .root = new  Node ();     }     public  void  add (int  i, int  x) {          add(i, x, 0 , n - 1 , root);     }     public  void  update (int  i, int  x) {          update(i, x, 0 , n - 1 , root);     }     public  int  query (int  i) {          return  query(i, 0 , n - 1 , root);     }     public  void  update (int  l, int  r, int  x) {          update(l, r, x, 0 , n - 1 , root);     }     public  int  sum (int  l, int  r) {          return  sum(l, r, 0 , n - 1 , root);     }     public  int  min (int  l, int  r) {          return  min(l, r, 0 , n - 1 , root);     }     public  int  max (int  l, int  r) {          return  max(l, r, 0 , n - 1 , root);     }          private  void  add (int  idx, int  x, int  s, int  t, Node cur) {         if (s == t) {             cur.val += x;              return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.updated) pushDown(s, c, t, cur);          if (idx <= c) add(idx, x, s, c, cur.lChild);         else  add(idx, x, c + 1 , t, cur.rChild);         pushUp(cur);      }          private  void  update (int  idx, int  x, int  s, int  t, Node cur) {         if (s == t) {             cur.val = x;              return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.updated) pushDown(s, c, t, cur);          if (idx <= c) update(idx, x, s, c, cur.lChild);         else  update(idx, x, c + 1 , t, cur.rChild);         pushUp(cur);       }          private  int  query (int  i, int  s, int  t, Node cur) {         if (s == t) return  cur.val;         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.updated) pushDown(s, c, t, cur);          if (i <= c) return  query(i, s, c, cur.lChild);         else  return  query(i, c + 1 , t, cur.rChild);     }          private  void  update (int  l, int  r, int  x, int  s, int  t, Node cur) {         if (l <= s && t <= r){              cur.val = (t - s + 1 ) * x;              if (s != t) {                  cur.lazy = x;                  cur.updated = true ;              }             return ;         }         addNode(cur);          int  c  =  s + (t - s) / 2 ;         if (cur.updated) pushDown(s, c, t, cur);          if (l <= c) update(l, r, x, s, c, cur.lChild);         if (r > c) update(l, r, x, c + 1 , t, cur.rChild);         pushUp(cur);      }          private  int  sum (int  l, int  r, int  s, int  t, Node cur) {         if (l <= s && t <= r) return  cur.val;          addNode(cur);          int  c  =  s + (t - s) / 2 , sum = 0 ;         if (cur.updated) pushDown(s, c, t, cur);          if (l <= c) sum += sum(l, r, s, c, cur.lChild);         if (r > c) sum += sum(l, r, c + 1 , t, cur.rChild);         return  sum;     }          private  int  min (int  l, int  r, int  s, int  t, Node cur) {         if (s == t) return  cur.val;          addNode(cur);          int  c  =  s + (t - s) / 2 , lmin = Integer.MAX_VALUE, rmin = Integer.MAX_VALUE;         if (cur.updated) pushDown(s, c, t, cur);          if (l <= c) lmin = min(l, r, s, c, cur.lChild);         if (r > c) rmin = min(l, r, c + 1 , t, cur.rChild);         return  Math.min(lmin, rmin);     }          private  int  max (int  l, int  r, int  s, int  t, Node cur) {         if (s == t) return  cur.val;         addNode(cur);         int  c  =  s + (t - s) / 2 , lmax = Integer.MIN_VALUE, rmax = Integer.MIN_VALUE;         if (cur.updated) pushDown(s, c, t, cur);         if (l <= c) lmax = max(l, r, s, c, cur.lChild);         if (r > c) rmax = max(l, r, c + 1 , t, cur.rChild);         return  Math.max(lmax, rmax);     }          private  void  pushUp (Node cur) {         Node  lChild  =  cur.lChild, rChild = cur.rChild;         cur.val = lChild.val + rChild.val;     }          private  void  pushDown (int  s, int  c, int  t, Node cur) {         Node  lChild  =  cur.lChild, rChild = cur.rChild;         lChild.val = (c - s + 1 ) * cur.lazy;          lChild.lazy = cur.lazy;          lChild.updated = true ;         rChild.val = (t - c) * cur.lazy;         rChild.lazy = cur.lazy;         rChild.updated = true ;         cur.lazy = 0 ;          cur.updated = false ;     }          private  void  addNode (Node node) {         if (node.lChild == null ) node.lChild = new  Node ();         if (node.rChild == null ) node.rChild = new  Node ();     } } 
总结 
本文从基本的静态线段树到动态线段树,介绍了如下内容。
基本原理:  通过 「完全二叉树下标性质」  和 「分治算法」  来理解基本线段树的工作原理。基本 (静态) 线段树:  首先实现了支持「单点修改」和「区间查询」的基本线段树,可解决 307. 区域和检索 - 数组可修改  。懒惰标记:  引入 「懒惰标记」  和 「延迟修改」  的概念,用以实现 O ( l o g n ) O(logn) O ( l o g n ) 离散化:  当区间问题与区间元素的绝对位置无关而只与它们的相对位置有关时,可以采用 「离散化」  压缩区间范围,典型题目如 699. 掉落的方块  。具体的离散化方法包括 「紧离散」  和 「松离散」  。动态开点:  当区间问题具有「强制在线 」的特点时,由于无法离散化,迫使我们思考能否实现 「动态开点」   (动态地创建结点) 的线段树。典型题目如 715. Range 模块  。
 
文中我们给出了如下十种线段树的完整的类代码。
线段树类 
描述 
 
 
1. S e g m e n t T r e e B a s i c 1 SegmentTreeBasic1 S e g m e n tT ree B a s i c 1  
基本静态线段树,不带懒惰标记,只支持单点修改和区间查询,实时维护 n u m s nums n u m s  
 
2. S e g m e n t T r e e B a s i c 2 SegmentTreeBasic2 S e g m e n tT ree B a s i c 2  
基本静态线段树,不带懒惰标记,只支持单点修改和区间查询,不维护 n u m s nums n u m s  
 
3. S e g m e n t T r e e A d d SegmentTreeAdd S e g m e n tT ree A dd  
静态线段树,带懒惰标记,支持单点修改/单点查询/增量式区间修改/区间查询 
 
4. S e g m e n t T r e e U p d a t e SegmentTreeUpdate S e g m e n tT ree U p d a t e  
静态线段树,带懒惰标记,支持单点修改/单点查询/覆盖式区间修改/区间查询 
 
5. D y n a m i c S e g m e n t T r e e A r r a y A d d DynamicSegmentTreeArrayAdd Dy nami c S e g m e n tT ree A rr a y A dd  
结点数组法动态线段树,带懒惰标记,支持单点修改/单点查询/增量式区间修改/区间查询 
 
6. D y n a m i c S e g m e n t T r e e A r r a y U p d a t e DynamicSegmentTreeArrayUpdate Dy nami c S e g m e n tT ree A rr a y U p d a t e  
结点数组法动态线段树,带懒惰标记,支持单点修改/单点查询/覆盖式区间修改/区间查询 
 
7. D y n a m i c S e g m e n t T r e e P o i n t e r A d d DynamicSegmentTreePointerAdd Dy nami c S e g m e n tT ree P o in t er A dd  
结点指针法动态线段树,带懒惰标记,支持单点修改/单点查询/增量式区间修改/区间查询 
 
8. D y n a m i c S e g m e n t T r e e P o i n t e r U p d a t e DynamicSegmentTreePointerUpdate Dy nami c S e g m e n tT ree P o in t er U p d a t e  
结点指针法动态线段树,带懒惰标记,支持单点修改/单点查询/覆盖式区间修改/区间查询 
 
9. S e g m e n t T r e e B a s i c R a n g e M a x SegmentTreeBasicRangeMax S e g m e n tT ree B a s i c R an g e M a x  
区间最大值基本静态线段树,无懒标记,不维护 n u m s [ i ] nums[i] n u m s [ i ]  
 
10. S e g m e n t T r e e B a s i c SegmentTreeBasic S e g m e n tT ree B a s i c  
扩展区间查询基本静态线段树,无懒标记,不维护 n u m s [ i ] nums[i] n u m s [ i ]  
 
 
注意,1~8 的实现中,t r e e [ ] tree[] t ree [ ] t r e e [ ] tree[] t ree [ ] O ( l o g n ) O(logn) O ( l o g n ) 
线段树的应用是十分灵活而强大的,本文只讲解了作者所知的最基本的一些内容,更多的应用已然超出了作者的水平。好在这些内容已基本足够求解力扣上几乎所有线段树题目。
※ 推荐清华大学张昆玮写的一份 101 页的 ppt 材料 统计的力量 —— 线段树全接触  ,可作为线段树应用的总览材料。
实战应用 
文章更新日志 
[2022-09-18]
增加了少量内容,说明 t r e e [ ] tree[] t ree [ ] m a x max ma x m i n min min t r e e [ ] tree[] t ree [ ] O ( n ) O(n) O ( n ) O ( l o g n ) O(logn) O ( l o g n )  
 
[2022-08-03]