线段树是基于分治思想的二叉树,用来维护区间信息。可以在 lognlognlogn 的时间里执行区间修改和查询,此篇以维护区间和为例(不同情况差别对待啊)。
注:二叉树的空间要开 (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)sum+(6,6)sum(5,5)_{sum}+(6,6)_{sum}(5,5)sum+(6,6)sum。
设 lclclc 为 p<<1p<<1p<<1,rcrcrc 为 p<<1∣1p<<1|1p<<1∣1,aia_iai 维护数组,tritr_itri 维护整棵树。
1. 建树(build):
利用二分查找,从根结点(1)(1)(1)开始,依次建左儿子的树,再建右儿子的树,并在此过程中累加每个结点的 sumsumsum 值。
代码:
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(log n)O(log\,n)O(logn)。那么优势就摆明了。
这个加强版用了懒惰标记(懒标记),那么懒标记是神马玩意?
懒标记相当于是过年时父母给小孩分零花钱,但小孩太多,分的话时间复杂度就废了,所以父母就说:“零花钱先放我这,要用的时候再发下去。”
这时,父结点就有了一个懒标记 addaddadd,addaddadd 的值就是父结点要给每个子结点的
值。
如图:

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)时也要使用,因为此时子结点若是没有分配到值时,得出的答案会比正确值小。
线段树是一种基于分治策略的二叉树数据结构,用于快速处理区间信息,如区间和。它能在logn时间内完成区间修改和查询。在构建线段树时,从根节点递归地创建左右子树并累加节点值。区间加法操作通过更新节点的sum值实现,查找操作则通过分割和拼凑策略进行。线段树的加数ProMax版本引入懒惰标记,降低了区间加法的时间复杂度到logn,提高了效率。
1289

被折叠的 条评论
为什么被折叠?



