zoukankan      html  css  js  c++  java
  • 算法导论学习-线段树(1)

    1. 线段树简介:

    ---------------

    线段树是建立在线段的基础上,每个结点都代表了一条线段[a,b]。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为[a,(a + b) / 2],右结点代表的线段为[((a + b) / 2)+1,b]。若非元线段的编号为 i,那么他的左孩子编号为 2*i,右孩子为 2*i+1。下图是一棵典型的线段树。,一般的,我们把根节点的编号设为1,那么下图很显然的,[1,5]的编号为2,[6,10]的编号为3,其他节点也以此类推。

    2. 线段树有什么用:

    -------------------

    我么知道一棵线段树上的每个节点都至少有两个域:left 和 right,分别表示一个区间的上限和下限。但是如果只是这样的话,线段树也是中看不中用的。事实上,我们在使用线段树的时候,会视问题的不同给树的节点添加域。在这些域中保存了某种动态维护的信息,这些域使得线段树具有极大的灵活性,可以适应不同的需求。下面举一个例子来看一看线段树的应用:

    桌子上零散地(放着若干个木条,桌子的后方是一堵墙。如下图所示(下图把墙虚拟的等分成了8份)。现在从桌子的前方射来一束平行光, 把木棍的影子投射到了墙上。求影子的总宽度是多少?(这里假设木棍的长度是正整数,且任一木棍都刚刚好在一些“连续数字对应的墙上”,比如最下方的那根木棍刚好对应了数字2,3,4。)这道题最蛮力的办法是开一个数组,比如就这道题来说,开一个数组A[1,...8]。然后给数组赋初值A[i] = 0, 表示第i段墙没有阴影。然后根据每根木条的阴影的范围修改A数组的值。比如对于最下面的那根木条,A[2]=A[3]=A[4]=1,1表示有阴影。这种方法的时间复杂度为O(n*range)。(n表示木条根数,range表示墙的宽度)

    现在我们换用线段树来做,给线段树每个节点增加一个域cover。cover=1表示该结点所对应的区间被完全覆盖,cover=0表示该结点所对应的区间未被完全覆盖。好了,现在线段树节点有3个域:left,right,cover。

    ###第一步:建立线段树

     1 const int max_size=32010;
     2 struct segmentTree{
     3     int left,right;
     4     int cover;
     5 }a[max_size*3];
     6 int cnt[max_size];
     7 void buildTree(int l,int r,int step){
     8     a[step].left=l;
     9     a[step].right=r;
    10     a[step].cover=0;
    11     if(l==r) return;
    12     int mid=(a[step].left+a[step].right)/2;
    13     buildTree(l,mid,step*2);
    14     buildTree(mid+1,r,step*2+1);
    15 }

    ###第二步:执行插入动作(我们依次把木条阴影插入到线段树中),代码如下:

     1 void updateSegment(int L,int R,int step){
     2         if(L==a[step].left&&R==a[step].right){
     3             a[step].cover++; return;
     4         }
     5         if(a[step].left==a[step].right) return;
     6         int mid=(a[step].left+a[step].right)/2;
     7         if(R<=mid) updateSegment(L,R,step*2);
     8         else if(L>=mid+1) updateSegment(L,R,step*2+1);
     9         else{
    10             updateSegment(L,mid,step*2);
    11             updateSegment(mid+1,R,step*2+1);
    12         }
    13 }

    下面四个图分别是依次插入木条[2,4],插入木条[3,5],插入木条[4,6]和插入木条[7,7]对应的线段树状态:

          

        

    ###第三步:查询(查询线段树中有阴影部分的长度)

    1 int count(int step){
    2     if(a[step].cover) return a[step].right-a[step].left+1;
    3     if(a[step].left==a[step].right){//leaf node
    4         if(a[step].cover) return 1;
    5         else return 0;
    6     }
    7     return count(step*2)+count(step*2+1);
    8 }

    上图中节点上方标注有cover=1,表示阴影覆盖了该节点表示的区域。根据图(d),我们可以得到最终的阴影长度:[2]*1+[3, 4]*1+[5, 6]*1+[7]*1=1+2*1+2*1+1=6。由于线段树插入,查询操作的耗时为O(lg n),所以时间复杂度有暴力枚举的O(n^2)降低到O(nlgn).

    ###拓展功能:删除

    回到最初的那个状态,如果我们把木条[3,5]拿掉会怎么样?我们发现最终的阴影部分还是6. 。。。下面是删除动作的代码:

     1 void del(int L,int R,int step){
     2     if(L==a[step].left&&R==a[step].right){//fully cover
     3         a[step].cover--; return;
     4     }
     5     if(a[step].left==a[step].right) return;
     6     int mid=(a[step].left+a[step].right)/2;
     7     if(R<=mid) del(L,R,step*2);
     8     else if(L>=mid+1) del(L,R,step*2+1);
     9     else{
    10         del(L,mid,step*2);
    11         del(mid+1,R,step*2+1);
    12     }
    13 }

    假设我们在已经插入了四根木条的情况下,对应图(d),抽调木条[3,5],那么线段树的状态转移如下:

      

    以上是线段树的区段动态修改与查询的应用,后续会有线段树的其他应用的介绍,To be continue......

  • 相关阅读:
    C库函数笔记
    曼彻斯特及差分曼彻斯特编码
    VS2008 由于应用程序配置不正确,应用程序未能启动。重新安装应用程序可能会纠正这个问题。
    刚子扯谈:酒装狗熊胆
    poj1410
    新游戏《真·方块无双》发布-穿越混世过险境,运筹方块化无双
    hdu4267 A Simple Problem with Integers
    【转载】变量的存储类型
    【转载】硬盘存储原理和内部结构
    【转载】让windows使用linux系统的命令
  • 原文地址:https://www.cnblogs.com/fu11211129/p/4230000.html
Copyright © 2011-2022 走看看