zoukankan      html  css  js  c++  java
  • 线段树基础详解

    转载自:点击打开链接(基础版)

    进阶版

    基本概念:

    存储结构是怎样的?

    线段树是一种二叉树,当然可以像一般的树那样写成结构体,指针什么的。
    但是它的优点是,它也可以用数组来实现树形结构,可以大大简化代码。
    数组形式适合在编程竞赛中使用,在已经知道线段树的最大规模的情况下,直接开足够空间的数组,然后在上面建立线段树。
    简单的记法: 足够的空间 = 数组大小n的四倍。 
    实际上足够的空间 =  (n向上扩充到最近的2的某个次方)的两倍。

    举例子:假设数组长度为5,就需要5先扩充成8,8*2=16.线段树需要16个元素。如果数组元素为8,那么也需要16个元素。
    所以线段树需要的空间是n的两倍到四倍之间的某个数,一般就开4*n的空间就好,如果空间不够,可以自己算好最大值来省点空间。

    怎么用数组来表示一颗二叉树呢?假设某个节点的编号为v,那么它的左子节点编号为2*v,右子节点编号为2*v+1。
    然后规定根节点为1.这样一颗二叉树就构造完成了。通常2*v在代码中写成 v<<1 。 2*v+1写成 v<<1|1 。

    以下以维护数列区间和的线段树为例,演示最基本的线段树代码。

    (0)定义:

    1. #define maxn 100007  //元素总个数  
    2. #define ls l,m,rt<<1  
    3. #define rs m+1,r,rt<<1|1  
    4. int Sum[maxn<<2],Add[maxn<<2];//Sum求和,Add为懒惰标记   
    5. int A[maxn],n;//存原数组数据下标[1,n]   

    (1)   建树:

    (2) //PushUp函数更新节点信息 ,这里是求和  

    (3) void PushUp(int rt){Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];}  

    (4) //Build函数建树   

    (5) void Build(int l,int r,int rt){ //l,r表示当前节点区间,rt表示当前节点编号  

    (6)     if(l==r) {//若到达叶节点   

    (7)         Sum[rt]=A[l];//储存数组值   

    (8)         return;  

    (9)     }  

    (10)          int m=(l+r)>>1;  

    (11)          //左右递归   

    (12)          Build(l,m,rt<<1);  

    (13)          Build(m+1,r,rt<<1|1);  

    (14)          //更新信息   

    (15)          PushUp(rt);  

    (16)      }  

    (2)点修改:

    假设A[L]+=C:

    1.  void Update(int L,int C,int l,int r,int rt){//l,r表示当前节点区间,rt表示当前节点编号  

    2.      if(l==r){//到叶节点,修改   

    3.          Sum[rt]+=C;  

    4.          return;  

    5.      }  

    6.      int m=(l+r)>>1;  

    7.      //根据条件判断往左子树调用还是往右   

    8.      if(L <= m) Update(L,C,l,m,rt<<1);  

    9.      else       Update(L,C,m+1,r,rt<<1|1);  

    10.     PushUp(rt);//子节点更新了,所以本节点也需要更新信息   

    11. }   

     

     

    (3)区间修改:

    1. void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号   
    2.     if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内   
    3.         Sum[rt]+=C*(r-l+1);//更新数字和,向上保持正确  
    4.         Add[rt]+=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整  
    5.         return ;   
    6.     }  
    7.     int m=(l+r)>>1;  
    8.     PushDown(rt,m-l+1,r-m);//下推标记  
    9.     //这里判断左右子树跟[L,R]有无交集,有交集才递归   
    10.     if(L <= m) Update(L,R,C,l,m,rt<<1);  
    11.     if(R >  m) Update(L,R,C,m+1,r,rt<<1|1);   
    12.     PushUp(rt);//更新本节点信息   
    13. }   

    (4)区间查询:

    询问A[L,R]的和

    首先是下推标记的函数:

    1.  void PushDown(int rt,int ln,int rn){  

    2.      //ln,rn为左子树,右子树的数字数量。   

    3.      if(Add[rt]){  

    4.          //下推标记   

    5.          Add[rt<<1]+=Add[rt];  

    6.          Add[rt<<1|1]+=Add[rt];  

    7.          //修改子节点的Sum使之与对应的Add相对应   

    8.          Sum[rt<<1]+=Add[rt]*ln;  

    9.          Sum[rt<<1|1]+=Add[rt]*rn;  

    10.         //清除本节点标记   

    11.         Add[rt]=0;  

    12.     }  

    13. }  

    然后是区间查询的函数:

    1. int Query(int L,int R,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号  
    2.     if(L <= l && r <= R){  
    3.         //在区间内,直接返回   
    4.         return Sum[rt];  
    5.     }  
    6.     int m=(l+r)>>1;  
    7.     //下推标记,否则Sum可能不正确  
    8.     PushDown(rt,m-l+1,r-m);   
    9.       
    10.     //累计答案  
    11.     int ANS=0;  
    12.     if(L <= m) ANS+=Query(L,R,l,m,rt<<1);  
    13.     if(R >  m) ANS+=Query(L,R,m+1,r,rt<<1|1);  
    14.     return ANS;  
    15. }   

    (5)函数调用:

    1. //建树   
    2. Build(1,n,1);   
    3. //点修改  
    4. Update(L,C,1,n,1);  
    5. //区间修改   
    6. Update(L,R,C,1,n,1);  
    7. //区间查询   
    8. int ANS=Query(L,R,1,n,1);  

  • 相关阅读:
    nodeJs-querystring 模块
    nodeJs-process对象
    nodejs-Path模块
    nodejs-os模块
    nodejs-CommonJS规范
    nodejs-Events模块
    nodejs-Http模块
    nodejs-Cluster模块
    转:AOP与JAVA动态代理
    转:jdk动态代理实现
  • 原文地址:https://www.cnblogs.com/bryce1010/p/9387378.html
Copyright © 2011-2022 走看看