• 线段树入门


    线段树是用于解决区间上问题的数据结构。用了分治的思想。

    可以用线段树解决的问题通常都符合结合律。及满足一个询问包含的多个区间,可以用一个或几个更大的区间表示。(例如sum,min,max,xor...)

    也就是说,父亲的值由他的孩子决定,通过孩子维护自己。而父亲的值是他的孩子集合的解。

    比方说,在上面的图片里。A[0...1]的值由A[0],A[1]共同维护,A[0...1]的值是包含A[0],A[1]的集合的解。

    因此,通过对线段树的维护,我们可以很快地得到我们想要的区间内的信息。

    我们将每一个区间(包括叶子结点)进行编号,从上到下从左到右。

    我们可以发现线段树是一棵二叉树。

    在上方的图片中,一共有10个叶子节点,但一共有19个节点。所以一般来说我们会开一个数组,长度在原始数据长度 L的4倍。

    我们有,对任意非叶子节点 p,他的左儿子为 p*2,右儿子为 p*2+1。

    在实际使用中,我们用位运算来获得一点加速。

    1 // find sons
    2 inline ll ls(ll p) {return p<<1;} // 计算左儿子的编号
    3 inline ll rs(ll p) {return p<<1|1;} // 计算右儿子的编号

    二进制左移一位代表 *2。左移后最后一位一定为0,所以 |1就相当于 +1。

    我们在函数的最前面加 inline以减少不必要的信息入栈。尤其对于递归次数少,执行代码短的函数。

    使用一个函数来维护 p的值。(在本文章里,我们已求区间和为例)

    1 inline void up(ll p) // 用于维护 p的值
    2 {
    3     t[p]=t[ls(p)]+t[rs(p)];
    4 }

    在拥有维护本层值的函数后,我们就可以建树了。

    我们从第一层开始递归建树。

    如果,当我现在包含的区间(l,r),l=r时,说明我已经分到了叶子节点。那我直接将本节点的值赋为我输入的数据。

    否则,我不断将(l,r)分为(l,mid)和(mid+1,r)进行建树。

    因为递归是先向下,后向上。所以我们在回溯时进行信息的维护。

    1 // build the tree
    2 inline void build(ll p,ll l,ll r)
    3 {
    4     if(l==r) {t[p]=a[l];return;}
    5     ll mid=(l+r)>>1;
    6     build(ls(p),l,mid);
    7     build(rs(p),mid+1,r);
    8     up(p);
    9 }

    接下来我们介绍有关线段树的修改。(本文章以加一个数为例)

    每次修改一个区间或单点时,由于完全二叉树的深度为logN。所以复杂度为O(NlogN)。

    但线段树的优点在于延迟更新。复杂度可以降至O(logN)。

    我们用一个数组 tag记录 p的更新。

    每次修改或查询时,只有用到 p下的区间时才会真正下传。

    如果要修改区间完全包含此区间,那么直接更新此区间的值与 tag即可。

    在修改完回溯时重新更新 p的值。

    下传类似于维护本区间。

    对于左右儿子,其 tag加上父亲的 tag,值为区间长(b-a+1)乘以父亲的 tag。

    对于父亲,其 tag在下传后归零。

    1 inline void down(ll p,ll l,ll r)
    2 {
    3     ll mid=(l+r)>>1;
    4     tag[ls(p)]+=tag[p];
    5     t[ls(p)]+=(mid-l+1)*tag[p];
    6     tag[rs(p)]+=tag[p];
    7     t[rs(p)]+=(r-(mid+1)+1)*tag[p];
    8     tag[p]=0;
    9 }

    这是修改的代码。

     1 // add the number
     2 inline void add(ll p,ll l,ll r,ll nl,ll nr,ll k)
     3 {
     4     if(nl<=l&&r<=nr)
     5     {
     6         tag[p]+=k;
     7         t[p]+=(r-l+1)*k;
     8     }
     9     down(p,l,r);
    10     ll mid=(l+r)>>1;
    11     if(nl<=mid) add(ls(p),l,mid,nl,nr,k);
    12     if(mid+1<=nr) add(rs(p),mid+1,r,nl,nr,k);
    13     up(p);
    14 }

    对于区间查询,使用同样的思路。

     1 // query the number
     2 inline ll query(ll p,ll l,ll r,ll nl,ll nr)
     3 {
     4     ll res=0;
     5     if(nl<=l&&r<=nr) return t[p];
     6     down(p,l,r);
     7     ll mid=(l+r)>>1;
     8     if(nl<=mid) res+=query(ls(p),l,mid,nl,nr);
     9     if(mid+1<=nr) res+=query(rs(p),mid+1,r,nl,nr);
    10     return res;
    11 }

    参考博客:https://pks-loving.blog.luogu.org/senior-data-structure-qian-tan-xian-duan-shu-segment-tree

  • 相关阅读:
    HeidiSQL
    PostgreSQL
    MariaDB
    NLog
    0 vs null
    忘带手机的那么一天
    江城子·己亥年戊辰月丁丑日话凄凉
    单体 VS 微服务
    java面向对象(四)之重写、重载
    java面向对象(三)之抽象类,接口,向上转型
  • 原文地址:https://www.cnblogs.com/Vty66CCFF/p/11136005.html
Copyright © 2020-2023  润新知