线段树是基于分治思想的二叉树,用来维护区间信息。可以在 l o g n logn logn 的时间里执行区间修改和查询,此篇以维护区间和为例(不同情况差别对待啊)。
注:二叉树的空间要开 ( 4 × N ) (4\times N) (4×N)。
所谓线段树,就是叶子结点的值是输入的数组的值,而非叶子结点的值便是左儿子的值加上右儿子的值,例: ( 5 , 5 ) (5,5) (5,5) 和 ( 6 , 6 ) (6,6) (6,6) 的父结点是 ( 5 , 6 ) (5,6) (5,6),值是 ( 5 , 5 ) s u m + ( 6 , 6 ) s u m (5,5)_{sum}+(6,6)_{sum} (5,5)sum+(6,6)sum。
设 l c lc lc 为 p < < 1 p<<1 p<<1, r c rc rc 为 p < < 1 ∣ 1 p<<1|1 p<<1∣1, a i a_i ai 维护数组, t r i tr_i tri 维护整棵树。
1. 建树(build):
利用二分查找,从根结点 ( 1 ) (1) (1)开始,依次建左儿子的树,再建右儿子的树,并在此过程中累加每个结点的 s u m sum sum 值。
代码:
void build(int p,int l,int r){
tr[p].l=l,tr[p].r=r,tr[p].sum=a[l];
if(l==r)return;
int m=(l+r)>>1;
build(lc,l,m);
build(rc,m+1,r);
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
2. 加数(update):
也是从根结点开始,顺着要加的数的方向搜,并依次加上值。
有张图好理解多了。
代码:
void update(int p,int x,int k){
tr[p].sum+=k;
if(tr[p].l==x&&tr[p].r==x)return;
int m=(tr[p].l+tr[p].r)>>1;
if(x<=m)update(lc,x,k);
if(x>m)update(rc,x,k);
}
3. 查找(find):
核心:分割、拼凑
从根结点开始。
有三种情况:
-
需查找的范围覆盖了当前所在的范围,那就在答案中加上当前的值,并且返回。
-
需查找的范围位于当前范围的左侧,那就搜左儿子。
-
需查找的范围位于当前范围的右侧,那就搜右儿子。
最后输出拼凑所得的答案即可。
代码:
int find(int p,int l,int r){
if(l<=tr[p].l&&tr[p].r<=r)return tr[p].sum;
int sum=0;
int m=(tr[p].l+tr[p].r)>>1;
if(l<=m)sum+=find(lc,l,r);
if(r>m) sum+=find(rc,l,r);
return sum;
}
4.加数 Pro Max(可用于区间加数):
进行区间加数时,如果运用原版的那个,时间复杂度就是 O ( n ) O(n) O(n),而这个加强版的时间复杂度成功维护在了 O ( l o g n ) O(log\,n) O(logn)。那么优势就摆明了。
这个加强版用了懒惰标记(懒标记),那么懒标记是神马玩意?
懒标记相当于是过年时父母给小孩分零花钱,但小孩太多,分的话时间复杂度就废了,所以父母就说:“零花钱先放我这,要用的时候再发下去。”
这时,父结点就有了一个懒标记
a
d
d
add
add,
a
d
d
add
add 的值就是父结点要给每个子结点的
值。
如图:
1~4 的区间要集体加 2,那就从根结点向下搜,若当前结点可以被目标区间完全覆盖,那就不再向下分配了,而是在此处打一个懒标记记录下面的结点要加的值即可。
如果没有完全覆盖,那就将要加的值向下分配,将当前懒标记清空。
新设一个函数为 pushdowm(简称pd)。用于向下分配。
代码:
void pd(int p){
if(tr[p].add>0){
tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;
tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
tr[lc].add+=tr[p].add;
tr[rc].add+=tr[p].add;
tr[p].add=0;
}
}
void update(int p,int x,int y,int k){
if(x<=tr[p].l&&tr[p].r<=y){
tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
tr[p].add+=k;
return;
}
int m=(tr[p].l+tr[p].r)>>1;
pd(p);
if(x<=m)update(lc,x,y,k);
if(m<y)update(rc,x,y,k);
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
切记:向下分配的函数 pd 在查询(find)时也要使用,因为此时子结点若是没有分配到值时,得出的答案会比正确值小。