2014-09-12 21:49:59 -> 2014-10-04 16:59:44
花了挺长的一段时间学习线段树,所刷的题:POJ线段树20题汇总,Hdu题库数道+比赛题2道。
与刚开始学连函数结构都要翻书相比,现在线段树已经实现自己较熟练地手敲。线段树这种结构,主要优势在于把区间问题从O(n)的复杂度优化到O(logn)。
几个注意点:
No.1:线段树的数组通常要开到叶子节点数的四倍(原因:刚好存满N个节点的线段树有趋近于2N个节点,而为了防溢出,要建出第N+1个叶子节点,那么根节点要再开一大棵子树出来,所以是4N)
No.2:有些线段树题目需要离散化,而线段树节点区间表示离散化后的值。
(1)建树,Build_tree
1、叶子节点为点的情况:
最为常见的建树方式,边界条件:l == r,递归方式:Build_tree(lp,l,mid) , Build_tree(rp,mid + 1,r)
2、叶子节点为单位边的情况:
这种情况中每两个相邻叶子有重叠点,如 (1,2) 和 (2,3),边界条件:l + 1 == r,递归方式:Build_tree(lp,l,mid) , Build_tree(rp,mid,r)
3、节点赋值:
可以在建树函数中嵌入读入语句,也可以赋值为离散化后的数组里的值
(2)更新,Update_tree
1、点更新:
最简单的更新方式,边界条件:l == r ,递归方式:根据点的相对位置只进入左 / 右子树中一棵
2、(需查询型)区间更新:
批量更改点,需要用到lazy思想,如果当前区间完全包含于需更新区间,则直接更新完并返回而不继续深入。这类更新需要在Query操作,并在其中判断是否继续深入。因此这种情况通常让每个树节点维护一个cover值,用来记录完全更新这个含义。
边界条件:ql <= l && r <= qr
递归方式:根据询问区间,if(ql <= mid) Update(左子树), if(qr > mid) Update(右子树)
3、(无需查询型)精确区间更新:
这类更新大多运用在扫描线中,精确更新的好处在于不用写查询函数,而每次查询只用到根节点的信息,这让扫描线写起来更飘逸。
边界条件:ql == l && r == qr
递归方式:因为是精确更新所以要控制好进入左右子树的区间范围(防止死循环)
if(ql >= right_son_l) Update_tree(ql,qr,右子树) (左子树完全包含更新区间)
else if(qr <= left_son_r) Update_tree(ql,qr,左子树) (右子树完全包含更新区间)
else{ Update_tree(ql,left_son_r,左子树),Update_tree(right_son_l,qr,右子树)}
(3)查询,Query
这个函数就大同小异了,边界条件:ql <= l && r <= qr,递归方式:if(ql <= mid) Query(左子树), if(qr > mid) Query(右子树)
(4)下推,Push_down
这个函数对应于每个节点的cover值,是lazy思想的体现。在某此更新中如果发现所遍历到的点在之前cover非零(即有区间完全更新的含义),那么就需要Push_down函数来把当前区间的信息下推给它的两个子树。注意点:下推完后自己的cover值要归零。(这里归零的含义应理解为标记为没有区间完全更新的含义)
几个经典题目:POJ 3667,这题把区间操作考察了个遍,然后每个节点需要维护的值也很多。
POJ 3695,扫描线好题,写完这题,扫描线基本上没问题了。