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还没学会别的高级操作,所以先就此搁笔了,愿大家越来越强

    慢即是快,细则是能,于小处铸迤逦
  • 相关阅读:
    C# Debugger.IsAttached 调试启动浏览器 VS if DEBUG 启动调试内容
    【转载】如何三个月从零基础到C#中级程序员
    【转载】如何成为一个高级程序员
    如何找回QQ聊天记录、语音、图片?
    hexo博客yili主题个性化自定义教程(1) ——借鉴中学习,初认yili主题
    2019hexo博客部署到coding该绕的坑-奥怪的小栈
    2019Hexo博客Next主题深度美化 打造一个炫酷博客(2)-奥怪的小栈
    2019软件工程专业大学排名附官网-奥怪的小栈
    浅谈互联网+足球
    2019基于Hexo快速搭建个人博客,打造一个炫酷博客(1)-奥怪的小栈
  • 原文地址:https://www.cnblogs.com/Hale522520/p/10186298.html
Copyright © 2011-2022 走看看