数据结构之线段树进阶(区间更新&&lazy标记)

之前说了线段树的点更新和区间求和。其实点更新是区间更新的一种最基础的做法。我们把一个点想像成一个区间的话,不就是最简单的区间更新了嘛。
为什么要把区间更新和点更新分开来看呢?假如我们对区间[l,r]进行更新,我们对这一段区间进行r-l+1次点更新不就行了嘛?这样结果确实没有错误,但是这样的话,时间复杂度会非常高,区间更新的题目如果这么做肯定会超时的。那么我们就引进了区间更新了。
这里引入了一个标记数组,lazy[maxn<<2]。lazy数组是区间更新中的精髓所在,lazy的意思就是懒的意思。我们对于每一次区间更新,我们并不直接一次更新到底,而是用lazy标记着更新了多少,等到需要的时候,我们再更新。
简单的说就是,我们把向下的修改先储存起来,而对于每个查询我们在向上传递答案的时候加上这些修改的值。
那区间更新+区间求和来说:
对于任意一段[l,r]区间加上一个数v,然后对任意区间求和。

inline void pushup(int cur)//pushup和之前的含义是一样的
{
	p[cur].sum=p[cur<<1].sum+p[cur<<1|1].sum;
}
inline void pushdown(int cur)//重点重点重点
{
	if(p[cur].lazy)//如果存在延迟更新的话
	{
		p[cur<<1].lazy+=p[cur].lazy;
		p[cur<<1|1].lazy+=p[cur].lazy;//左右儿子节点的lazy加上父亲节点的lazy
		p[cur<<1].sum+=(p[cur<<1].r-p[cur<<1].l+1)*p[cur].lazy;
		p[cur<<1|1].sum+=(p[cur<<1|1].r-p[cur<<1|1].l+1)*p[cur].lazy;//每个节点都加上了lazy,这样的话,一共就加上了区间长度个lazy。
		p[cur].lazy=0;//执行完上面的操作后就将lazy清零。
	}
}

区间更新模板

const int maxx=1e5+100;
struct node{
	int l;
	int r;
	int sum;
	int lazy;
}p[maxx<<2];
inline void pushup(int cur)
{
	p[cur].sum=p[cur<<1].sum+p[cur<<1|1].sum;
}
inline void pushdown(int cur)
{
	if(p[cur].lazy)
	{
		p[cur<<1].lazy+=p[cur].lazy;
		p[cur<<1|1].lazy+=p[cur].lazy;
		p[cur<<1].sum+=(p[cur<<1].r-p[cur<<1].l+1)*p[cur].lazy;
		p[cur<<1|1].sum+=(p[cur<<1|1].r-p[cur<<1|1].l+1)*p[cur].lazy;
		p[cur].lazy=0;
	}
}
inline void build(int l,int r,int cur)//一如既往的建树操作
{
	p[cur].l=l;
	p[cur].r=r;
	p[cur].sum=p[cur].lazy=0;
	if(l==r)
	{
		p[cur].sum=a[l];
		return ;
	}
	int mid=l+r>>1;
	build(l,mid,cur<<1);
	build(mid+1,r,cur<<1|1);
	pushup(cur);
}
inline void update(int l,int r,int cur,int v)
{
	int L=p[cur].l;
	int R=p[cur].r;
	if(l<=L&&R<=r)//如果当前区间在要更新的区间中,就可以停止递归,转为lazy标记
	{
		p[cur].sum+=(R-L+1)*v;
		p[cur].lazy+=v;
		return ;
	}
	pushdown(cur);//现在要更新下面的子节点了,因此我们要先将之前的lazy标记下放,否则会出错。
	int mid=L+R>>1;
	if(r<=mid) update(l,r,cur<<1,v);
	else if(l>mid) update(l,r,cur<<1|1,v);
	else 
	{
		update(l,mid,cur<<1,v);
		update(mid+1,r,cur<<1|1,v);
	}
	pushup(cur);
}
inline int query(int l,int r,int cur)//求和操作,和update的含义是一样的。
{
	int L=p[cur].l;
	int R=p[cur].r;
	if(l<=L&&R<=r) return p[cur].sum;
	pushdown(cur);
	int mid=L+R>>1;
	if(r<=mid) return query(l,r,cur<<1);
	else if(l>mid) return query(l,r,cur<<1|1);
	else return query(l,mid,cur<<1)+query(mid+1,r,cur<<1|1);
}

线段树的魅力并不在于这里,而是线段树和其他的算法巧妙的结合在一起,例如DP以及矩阵等,会产生很多有意思的算法。除此之外,线段树的区间合并以及扫描线等也是很有意思的。
努力加油a啊,(o)/~

发布了426 篇原创文章 · 获赞 24 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/starlet_kiss/article/details/104266122