线段树入门

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

可以用线段树解决的问题通常都符合结合律。及满足一个询问包含的多个区间,可以用一个或几个更大的区间表示。(例如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

posted @ 2019-07-05 13:51  敲可耐的螺旋藻  阅读(287)  评论(0编辑  收藏  举报