zoukankan      html  css  js  c++  java
  • 浅谈线段树(by Shine_hale)

    一. 线段树是什么?

    线段树,顾名思义,就是将区间变成线段进行处理
    如图可以看出,将1-10这个线段不断拆分,进而得到子节点;

    摘自互联网

    二、为什么要用线段树

    线段树修改简单,方便快捷,同时;在查询上可以使时间复杂度到达O(1),这很厉害了
    同时不同于RMQ问题,可以在线进行修改,不用花时间进行重构。
    但是线段树有个缺点有个较大的常数。
    话不多说,正式开始介绍,hale理解下的线段树大法

    线段树模板

    线段树,毕竟是树形结构,第一步当然是建树了
    首先要有准备工作,处理左儿子,右儿子的问题

    int ls(int p) {return p<<1;}//左儿子
    int rs(int p) {return p<<1|1;}//右儿子

    建树过程(递归建树)其实还有一种建树方法

    zkw线段树 (但是我不会告诉你我不会的)嘤嘤嘤

    void build(int p,int l,int r)
    { int mid=(l+r)>>1;
      if (l==r) { st[p].ans=a[l];return;}
      build(ls(p),l,mid);
      build(rs(p),mid+1,r);
      push_up(p); 
    }

    其实push_up本身就是向上回溯的过程
    我只是把这个过程单独列了出来

    void push_up(int p)
    { st[p].ans=st[ls(p)].ans+st[rs(p)].ans;}

    接着介绍如何区间修改
    为什么不介绍单点修改,其实很简单了,单点不就可以看成一个只包含自己的区间吗
    那么对于区间操作,我们考虑引入一个名叫“lazylazy tagtag”(懒标记)的东西——之所以称其“lazylazy”,是因为原本区间修改需要通过先改变叶子节点的值,然后不断地向上递归修改祖先节点直至到达根节点,时间复杂度最高可以到达O(nlogn)O(nlogn)的级别。但当我们引入了懒标记之后,区间更新的期望复杂度就降到了O(logn)O(logn)的级别且甚至会更低.

    void push_down(int p,int l,int r)
    { int mid=(l+r)>>1;
      st[ls(p)].add+=st[p].add;
      st[rs(p)].add+=st[p].add;
      st[ls(p)].ans+=st[p].add*(mid-l+1);
      st[rs(p)].ans+=st[p].add*(r-mid);
      st[p].add=0;
    }

    首先,懒标记的作用是记录每次、每个节点要更新的值,也就是delta,但线段树的优点不在于全记录(全记录依然很慢qwq),而在于传递式记录;

    整个区间都被操作,记录在公共祖先节点上;只修改了一部分,那么就记录在这部分的公共祖先上;如果四环以内只修改了自己的话,那就只改变自己。

    如果我们采用上述的优化方式的话,我们就需要在每次区间的查询修改时pushdown一次,以免重复或者冲突或者爆炸qwqqwq
    那么对于pushdown而言,其实就是纯粹的pushup的逆向思维(但不是逆向操作): 因为修改信息存在父节点上,所以要由父节点向下传导lazy tag
    那么问题来了:怎么传导pushdown呢?这里很有意思,开始回溯时执行pushup,因为是向上传导信息;那我们如果要让它向下更新,就调整顺序,在向下递归的时候pushdown就好了

    void update(int nl,int nr,int l,int r,int p,int k)
    { if (nl<=l&&nr>=r) 
      { st[p].ans+=k*(r-l+1);
        st[p].add+=k;return;}
      int mid=(l+r)>>1;
      push_down(p,l,r);
      if (nl<=mid) update(nl,nr,l,mid,ls(p),k);
      if (nr>mid) update(nl,nr,mid+1,r,rs(p),k);
      push_up(p);
    }

    好了,区间修改了之后就该区间求和了
    思想跟上面的差不多,大家自行理解就好了、

    int query(int qx,int qy,int l,int r,int p)
    { int res=0;
      if (qx<=l&&qy>=r) return st[p].ans;
      push_down(p,l,r);
      int mid=(l+r)>>1;
      if (qx<=mid) res+=query(qx,qy,l,mid,ls(p));
      if (qy>mid) res+=query(qx,qy,mid+1,r,rs(p));
      return res;
    }

    最后插一句
    区间乘法的维护需要注意先后顺序同时要注意取模,一定要一步一取模啊

    void push_down(int p,int l,int r)
    { int mid=(l+r)>>1;
      st[ls(p)].nul=(st[ls(p)].nul*st[p].nul)%Mod;
      st[rs(p)].nul=(st[rs(p)].nul*st[p].nul)%Mod;
      st[ls(p)].add=(st[ls(p)].add*st[p].nul+st[p].add)%Mod;
      st[rs(p)].add=(st[rs(p)].add*st[p].nul+st[p].add)%Mod;
      st[ls(p)].ans=(st[ls(p)].ans*st[p].nul+st[p].add*(mid-l+1))%Mod;
      st[rs(p)].ans=(st[rs(p)].ans*st[p].nul+st[p].add*(r-mid))%Mod;
      st[p].nul=1;
      st[p].add=0;
    }

    最后的最后,我知道大家不想听我扯这么多

    所以直接暴力上代码吧,嘤嘤嘤

    #include<bits/stdc++.h>
    using namespace std;
    const int M=200010;
    int m,n,k,p,a[M];
    struct node
    { int ans;
      int add;
    };
    node st[M<<2];
    int ls(int p) {return p<<1;}
    int rs(int p) {return p<<1|1;}
    void push_up(int p)
    { st[p].ans=st[ls(p)].ans+st[rs(p)].ans;}
    void build(int p,int l,int r)
    { int mid=(l+r)>>1;
      if (l==r) { st[p].ans=a[l];return;}
      build(ls(p),l,mid);
      build(rs(p),mid+1,r);
      push_up(p); 
    }
    void push_down(int p,int l,int r)
    { int mid=(l+r)>>1;
      st[ls(p)].add+=st[p].add;
      st[rs(p)].add+=st[p].add;
      st[ls(p)].ans+=st[p].add*(mid-l+1);
      st[rs(p)].ans+=st[p].add*(r-mid);
      st[p].add=0;
    }
    void update(int nl,int nr,int l,int r,int p,int k)
    { if (nl<=l&&nr>=r) 
      { st[p].ans+=k*(r-l+1);
        st[p].add+=k;return;}
      int mid=(l+r)>>1;
      push_down(p,l,r);
      if (nl<=mid) update(nl,nr,l,mid,ls(p),k);
      if (nr>mid) update(nl,nr,mid+1,r,rs(p),k);
      push_up(p);
    }
    int query(int qx,int qy,int l,int r,int p)
    { int res=0;
      if (qx<=l&&qy>=r) return st[p].ans;
      push_down(p,l,r);
      int mid=(l+r)>>1;
      if (qx<=mid) res+=query(qx,qy,l,mid,ls(p));
      if (qy>mid) res+=query(qx,qy,mid+1,r,rs(p));
      return res;
    }
    int main()
    { int x,y,z;
      scanf("%d",&n);
      for (int i=1;i<=n;i++)
      scanf("%d",&a[i]);
      build(1,1,n);
      scanf("%d",&m);
      for (int i=1;i<=m;i++)
      { scanf("%d",&k);  
        switch(k)
        { case 1:{scanf("%d%d%d",&x,&y,&z);
                  update(x,y,1,n,1,z);
                  break;}
          case 2:{scanf("%d%d",&x,&y);
                  printf("%d
    ",query(x,y,1,n,1));
                  break;}
        }
      }
      return 0;
    }

    hale还没学会别的高级操作,所以先就此搁笔了,愿大家越来越强

    慢即是快,细则是能,于小处铸迤逦
  • 相关阅读:
    Asp.net 动态添加Meta标签
    【转】在SharePoint Server 2010中更改“我的网站”
    SPQuery DateTime 类型查询
    Asp.net Web Application 打开 SharePoint 2010 Site 错误 The Web application at could not be found
    How To Create SharePoint 2010 Site Collection In Its Own DB
    C# 文件打印
    面试题 java集合
    《深入理解Java虚拟机》(六)堆内存使用分析,垃圾收集器 GC 日志解读
    《深入理解Java虚拟机》(五)JVM调优
    《深入理解Java虚拟机》(四)虚拟机性能监控与故障处理工具
  • 原文地址:https://www.cnblogs.com/Hale522520/p/10186298.html
Copyright © 2011-2022 走看看