这几天把之前学过的线段树又重新系统学习了一下。从网上参考了不少的资料,在尝试
了几个模板之后,最终参考了HH大神的博客。真的是功能齐全又好用。参考文章
http://www.notonlysuccess.com/index.php/segment-tree-complete/
线段树是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。a,b通常是整数。每一个
叶子节点表示了一个单位区间(长度为1)。对于每一个非叶结点所表示的结点[a,b],其左
儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b](除法去尾取整)。
如下图所示:
线段树适用于和区间统计有关的问题。比如某些数据可以按区间进行划分,按区间动态
进行修改,而且还需要按区间多次进行查询,那么使用线段树可以达到较快查询速度。
线段树共有如下几种:
1.点更新型
对于这种线段树,通常是向线段树中插入点,即对应一个叶子节点的信息,而线段树中
所有节点也都是记录的关于以该点为根的子树中已插入的点的统计信息,询问通常是问
线段树中某个区间对叶子节点的统计信息。
2.区间更新型
对于这种线段树,与第三种有一下共同特点:所有询问和插入操作都是以区间为单位
的,每次都是对一个区间进行操作。每个节点通常会用一个变量来记录以它为跟的子树
的相关信息,在回溯过程中更新此变量。当操作的区间能完整的覆盖一个节点对应的区
间时直接对该节点操作,不再向下操作。
这种线段树还有其独有的特点:当操作一个短线段时,这个短线段在线段树中由上至下
运行到自己对应的位置,这个过程中会经过若干个对应长线段的节点,在经过长线段
时,要把长线段的节点中的信息移动到其两个直接子节点中,然后再继续向下走。这样
可以保证区间信息存储位置的在树中的纵向专一性,即树中节点的直系血亲之间只能有
一个点记录信息。原因如下:在这种线段树的题通常具有以下性质:(1)新插入线段与旧
的线段重叠的部分的信息如果纵向分布,不存储在同一个节点中,则在回溯统计子树信
息过程很难计算。(2)新插入的线段与旧线段的重叠部分可以只保存新线段信息,这样才
能插入过程中完整覆盖一个节点时不用向下操作。
3.区间保留型
这种线段树除了与第二种线段树的共同特点外,还有一个重要特性就是即使旧线段与新
线段重叠了旧线段中的信息也仍然有意义,或者要求插入的线段必须保持在其插入的位
置不向下迭代。
其中保持位置的一种典型情况就是有删除操作。删除并不是随意的删除,每次删除的线
段与原来插入的线段相对应,只删除那些曾经插入过的线段。这种通常我们为了删除时
候方便,在短线段向下运行经过长线段的过程中不会把长线段向下迭代,因为长线段是
要被删除的,如果向下迭代删除时就没有办法对长线段原来的存储节点进行操作,只能
对其许许多多的后代节点中的一些(那些被迭代到了的节点)进行操作,复杂度极高。
这种线段树的题通常信息纵向分布也是可以回溯计算的。
之前写过的线段树各种版本:
。。。。。。
目前HH版本代码风格:
- MAXN为最大区间,节点要开为4倍,因为需要2*2^ [log2n] -1个元素 ([log2n]向上取整) ,其中2*2^ [log2n] -1 <= 4n -1
- pushup(int root)用于把当前结点的信息更新到父结点
- pushdown(int root,int L,int R)用于把当前结点的信息更新给儿子结点
- root表示当前子树的根(root),也就是当前所在的结点
四种线段树题目分类:
- 单点更新,只更新叶子节点,利用pushup(int root)函数更新到父节点 HDU_1166_敌兵布阵解题报告:点击打开链接
HDU 1754_I Hate It解题报告:点击打开链接
HDU 1294_Minimum Inversion Number解题报告:点击打开链接
HDU2795_Billboard解题报告:点击打开链接 - 成段更新(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候 POJ 3468_A Simple Problem with Integers
解题报告:点击打开链接
HDU1698_Just a Hook解题报告:点击打开链接 - 区间合并
这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并 - 扫描线
这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去
最典型的就是矩形面积并,周长并等题
未完待续。。。。。。