关于线段树

线段树是一种基于分治策略的二叉树数据结构,用于快速处理区间信息,如区间和。它能在logn时间内完成区间修改和查询。在构建线段树时,从根节点递归地创建左右子树并累加节点值。区间加法操作通过更新节点的sum值实现,查找操作则通过分割和拼凑策略进行。线段树的加数ProMax版本引入懒惰标记,降低了区间加法的时间复杂度到logn,提高了效率。
摘要由CSDN通过智能技术生成

线段树是基于分治思想二叉树,用来维护区间信息。可以在 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):

也是从根结点开始,顺着要加的数的方向搜,并依次加上值。

有张图好理解多了。
1

代码:
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):

核心:分割拼凑

从根结点开始。

有三种情况:

  1. 需查找的范围覆盖了当前所在的范围,那就在答案中加上当前的值,并且返回。

  2. 需查找的范围位于当前范围的左侧,那就搜左儿子。

  3. 需查找的范围位于当前范围的右侧,那就搜右儿子。

最后输出拼凑所得的答案即可。

代码:
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

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)时也要使用,因为此时子结点若是没有分配到值时,得出的答案会比正确值小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值