• 浅谈线段树


    线段树

    对于维护区间内的信息,我们可使用RMQ,但这种做法的缺点是无法快速修改,而线段树这种数据结构则可以实现实时的查询、修改(单点、区间)。

    原理:

    线段树是一种二叉搜索树,对于每个节点,他代表区间L~R的信息,而其两个子节点分别代表L~mid、mid+1~R的信息。

    建树:

    只需要遍历到每个叶子节点时赋值,回溯过程中再对树进行调整

    也可以进行N次单点修改,一般来说对总的时间复杂度影响不大

    void build(int l,int r,int p)
    {
       int mid=(l+r)>>1;
       if(l==r)
      {
           s[p]=v[l];
           return;
      }
       build(l,mid,lson);
       build(mid+1,r,rson);
    }

    区间查询:

    若想查询某一区间信息,我们可从根节点遍历。当遍历到的节点被查询区间完全覆盖的时候,便返回信息。

    在建树时,我们可以选择用堆式结构,或者动态开点。

    如要查询1~8的信息:

    节点1~10左右节点均有1~6的信息,向下查询

    节点1~5被查询区间完全包含,返回该点信息

    节点6~10的左区间包含查询区间信息,向左遍历

    节点6~8被查询区间完全包含,返回该点区间信息

    最后获得1~5、6~8的信息,再进行整合。

    如求和:

    int query_s(int l,int r,int x,int y,int p)
    {
      int mid=(l+r)>>1;
      if(x<=l&&y>=r) return s[p];
      int ret=0;
      if(x<=mid) ret+=query_s(l,mid,x,y,lson);
      if(y>mid) ret+=query_s(mid+1,r,x,y,rson);
      return ret;    
    }

    此段代码中,我们选择把该区间的左右范围传入函数,这样可以节省空间与时间。

    若使用堆式结构,那么lson = p<<1 , rson = p << 1 | 1 (p为该点的位置)

    若使用动态开点,lson rson则为数组

    单点修改:

    对于单点修改,首先,我们要找到该点在线段树中对应的位置,再将其权值修改,在回溯的过程中修改沿途的节点。

    若使用动态开点,我们则需要注意左右儿子节点是否被开出来过,这里我们用传引用的方式对p进行修改。

    动态开点版:

    void update(int l,int r,int x,int v,int &p)
    {
       int mid=(l+r)>>1;
       if(p==0) p=++cnt;
       if(l==r)
      {
           m[p]=v;
           s[p]=v;
           return;
      }
      if(x<=mid) update(l,mid,x,v,lson[p]);
      else update(mid+1,r,x,v,rson[p]);
      m[p]=max(m[lson[p]],m[rson[p]]);
      s[p]=s[lson[p]]+s[rson[p]];
    }

    堆式结构版:

    void update(int l,int r,int x,int v,int p)
    {
       int mid=(l+r)>>1;
       if(l==r)
      {
           m[p]=v;
           s[p]=v;
           return;
      }
      if(x<=mid) update(l,mid,x,v,lson);
      else update(mid+1,r,x,v,rson);
      m[p]=max(m[lson],m[rson]);
      s[p]=s[lson]+s[rson];
    }

    区间修改:

    我们可以看出,线段树可以很方便的进行单点修改与区间查询,但区间修改却略显麻烦。

    暴力来做,区间修改就是多次单点修改,那么单词修改时间复杂度就退化成了NlogN,十分不理想。

    在这里,我们加入lazy数组进行优化

    顾名思义,lazy数组可以减少一次区间修改的操作数(就是懒)

    原理:

    对于区间修改,我们可以不完成这个操作,而是像区间查询一样,把要修改的区间分割成线段树上对应的若干个节点,在这些节点的lazy上加上我们想要的值(这里讲解的是区间加和,覆盖同理)。等到查询的时候,我们再将lazy数组向下推,并且进行赋值之类的操作。

     

    int lz[maxn<<2];
    int tr[maxn<<2];
    int n;
    int v[maxn];
    #define lson p<<1
    #define rson p<<1|1
    void mark(int l,int r,int p,int v)
    {
       int len=(r-l+1);
       lz[p]+=v;
       tr[p]+=len*v;
    }
    void pushdown(int l,int r,int p)
    {
       int mid=(r+l)>>1;
      mark(l,mid,lson,lz[p]);
      mark(mid+1,r,rson,lz[p]);
      lz[p]=0;
    }                                                                                                    
    void update_node(int l,int r,int x,int v,int p)
    {
       if(l==r){
           tr[p]+=v;
           return ;
      }
       int mid=(l+r)>>1;
       if(lz[p]) pushdown(l,r,p);
       if(x<=mid) update_node(l,mid,x,v,lson);
       else update_node(mid+1,r,x,v,rson);
       tr[p]=tr[lson]+tr[rson];
    }
    void update(int l,int r,int x,int y,int v,int p)
    {
       int mid=(l+r)>>1;
       if(x<=l&&y>=r)
      {
           mark(l,r,p,v);
           return;
      }
       if(lz[p]) pushdown(l,r,p);
       if(x<=mid) update(l,mid,x,y,v,lson);
       if(y>mid) update(mid+1,r,x,y,v,rson);
        tr[p]=tr[lson]+tr[rson];
    }
    int query(int l,int r,int x,int y,int p)
    {
       int mid=(l+r)>>1;
       if(x<=l&&y>=r) return tr[p];
       if(lz[p])
       pushdown(l,r,p);
       int ret=0;
       if(x<=mid) ret+=query(l,mid,x,y,lson);
       if(y>mid) ret+=query(mid+1,r,x,y,rson);
       return ret;
    }



  • 相关阅读:
    利用@media screen实现网页布局的自适应
    心得体悟帖---200315(任何时候,都不要为不值得的人,不值得的事,费时间,费心力)
    心得体悟帖---200315(急啥,复习什么录什么)
    vue项目目录结构详解
    日常英语---200313(npm WARN deprecated vue-cli@2.9.6: This package has been deprecated in favour of @vue/cli)
    vuex是干什么的
    石川es6课程---4、箭头函数
    石川es6课程---3、变量let和常量const
    vue参考---eslink编码规范检查
    vue参考---vue项目结构
  • 原文地址:https://www.cnblogs.com/Marcelo/p/13798886.html
Copyright © 2020-2023  润新知