数据结构-线段树(二)

目录

懒标记

“懒”标记の介绍

“懒”标记の好处

“懒”标记的定义方式

 

区间更新 

区间更新

思想

题目练习 

题目の练习


在此之前,我们先引入一个概念——

“懒”标记の介绍

懒标记的作用,就是维护每一个节点的参数

懒标记顾名思义,就是要懒嘛!这里的懒是运用拟人的手法,懒标记就是记录当前状态,在更新操作的时候将这个状态下传

形象地说,懒标记就像寄存包裹一样,先把这个包裹(数据)存在这里,等有需要的时候再取(下传)

那么,为什么要用懒标记呢?

“懒”标记の好处

 试想一下,在还没有引入懒标记的时候,如果要频繁地将线段树的节点更新等做一些无用、重复操作,那么对于线段树而言,其时间复杂度还不如爆搜要小,因此,我们在这里引入了懒标记的概念

“懒”标记的定义方式

直接在结构体里边改就行了

struct Seg{
    int l,r;
    int val;//维护的值
    int lazy;//懒标记,我习惯写作tag,但这里方便理解就用lazy表示
};

 

区间更新

回到刚才的问题,那么区间更新应该怎么操作呢?

在没学过下面的内容之前,很容易想到将单点更新套入一个for循环里面嘛!

我可以告诉你这种算法的时间复杂度是O(n^2 log n)很明显碰到稍微大一点的数据肯定爆

那么什么才是区间更新的正确姿势呢?

思想

我们在这里为了方便,将想要更改的区间称之为母区间,包含在这里面的若干个区间称之为子区间

其实核心思想和区间查询差不多,主要是找到一个表示为子区间的结点就行了,没必要再向下更新!

如图所示:(我们将更改 [4,8] )

数据结构-线段树(二)_第1张图片

找到结点[4,4]and[5,8]就行了(图中用红色标识),不用再向下找

我们先设计找到后的代码:

找到一个结点表示的区间包含在目标区间内后,先更改这个结点所维护的信息(因为你要更新它的母区间啊)

问题来了,请思考一下下面两种方案哪种正确呢?

A.当然是一边更新一边下传啊!!!选我!!!

B.当然是先更新,等下次更新的时候再传啊!!!选我!!!

 

请自行思考1min!

 

 

 

 

盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~

盯~盯~——————————————————————————————————————————————思考线

盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~盯~

 

 

 

 

 

 

答案是A!

理由很简单

试想一下,如果本次更新的区间和下次不一样呢?!而且下次更新的区间刚好处于结点的两个子节点呢?!

如图:第一次更新[5,6],第二次更新[7,8]

数据结构-线段树(二)_第2张图片

于是乎选B的朋友在下传的时候就会出现混乱,之前要下传的和现在更新的好难处理啊!!!

如果选A的话,在更新的时候直接下传给左右子结点,多好!!!

 

在更改值的时候请注意,因为有些不是叶节点所以要注意维护的值应该怎么变化!

在“将[l,r]的值全部改为z并且求出某段的和”这种题目中,很明显要维护的值为区间之和

那么val(表示区间之和)=len(查到的子区间长度)* z(要修改的值)

小学乘法别问我为什么

即val=len*z

其中len=r-l+1

紧接着就更改它的懒标记,将其更改为想要更改的值

别忘了return ;

 

紧接着是下传操作(pushdown)

很多dalao用一个函数写了,这里我喜欢直接丢进更新函数中

就是将lazy标记下传给左右子,将左右子的维护的状态修改

 

情况和区间查询类似

将区间“腰斩”!!!

从中间折了之后往左右两边查,当然从中间查也分左右两种情况

图片请参照上一篇里面的区间查询

 

在查找之后记得向上传(pushup)

其实就是让母结点递归更新

 

于是乎,一个完整的更新代码就出来了!!!


void update(int k,int l,int r,int x,int y,int z)
{
	if(x>=l && y<=r)//找到子区间
	{
		tr[k].val=(r-l+1)*z;
		tr[k].lazy=z;
		return ;
	}
	//pushdown
	int mid=(l+r)>>1;
	if(tr[k].lazy)
	{
		tr[k>>1|1].lazy=tr[k].lazy,tr[k>>1].lazy=tr[k].lazy;
		tr[k>>1|1].val=(mid-l+1)*tr[k].lazy;
		tr[k>>1].val=(r-mid)*tr[k].lazy;
		tr[k].lazy=0;//记得给母结点的懒标记清零
	}
    //find,接着找下一个子区间
	if(y <= mid)
		update(k>>1,l,mid,x,y,z);
	else if(x > mid)
		update(k>>1|1,mid+1,r,x,y,z);
	else
	{
		update(k>>1,mid,x,y,z);
		update(k>>1|1,mid+1,r,x,y,z);
	}
	//pushup
	tr[k].val=tr[k>>2|1].val+tr[k>>1].val;
}

以前我也是直接写k作为当前结点下标,多方便!但是写到后面再加上看了dalao博客我才觉得记录左右端点太浪费空间了!

所以这个函数参数里的l,r就是当前结点的左右端点,建议大家也这样写。

注:

这里的k>>1 相当于 k*2代表的是左子节点的下标

k>>1|1相当于k*2+1代表的是右子节点的下标

 

题目の练习

下面的都是模板题

hdu1698

luogu3372

luogu3373(相对偏难)

 

请dalao多多指点!!!

你可能感兴趣的:(区间问题解决,C++,竞赛)