线段树懒标记详解

引入

在上一篇题解。我们详细讲解了单点修改区间查询的线段树。在这篇题解我们将要讲解区间修改区间查询的线段树。

懒标记

背景

我们发现虽然我们可以做到在 O ( l o g n ) O(log_{n}) O(logn)的时间内做到单点修改,但我们如果将一个区间修改,我们发现时间复杂度为 O ( n l o g n ) O(nlog_{n}) O(nlogn),比暴力还慢。那我们只能想一些其他方法了。

结构分析

我们先从线段树的结构入手:
线段树懒标记详解_第1张图片
还是这张图,红线为访问区间 [ 2 , 3 ] [2,3] [2,3]的顺序,我们发现虽说我们要修改点2,3。但我们如果不访问节点2,3,我们可以不更新它们的值。在这种思想下懒标记产生了。

定义

我们先将要修改的区间转化为若干个极大的节点(即当前区间包括在要求区间,且他的深度尽量低)。对于这些节点,以区间加为例,我们维护一个值 a d d add add,表示这个节点对于区间都加上了 a d d add add,我们通过 a d d add add直接快速更新当前节点的值,而不由子节点更新而来。在任意时刻,如果要访问子节点,那我们将标记下传即可。

结构体定义

我们发现之前的结构体需要一些修改。

const int N=1e5+5;
struct tre{
	int mx,l,r,add;
//mx为保存数的信息(最大值等等),对应区间为[l,r],add为加法懒标记,表示这个区间加上了add
};

建树

建树和之前差不多,就是要给 a d d add add赋初值。

void build(int u,int l,int r){//建树
		tree[u]={-0x3f3f3f3f,l,r,0};//初始值,add=0表示没有懒标记
		if(l==r){tree[u].mx=w[l];return;}//边界
		int mid=(l+r)/2;
		build(u*2,l,mid);build(u*2+1,mid+1,r);//建左右子树
		pushup(u);//更新
	}

直接加

在进行区间修改之前,我们先要解决一些直接将区间加上一个数的函数。

void addline(int u,int v){tree[u].mx+=v;tree[u].add+=v;}//将u对于区间加上v

标记下传

懒标记下传即直接将左右子节点加上当前节点的懒标记即可。记得懒标记下传后就要清空。

void pushdown(int u){//标记下传
		if(!tree[u].add)return ;//没有标记就不下传
		addline(u*2,tree[u].add);addline(u*2+1,tree[u].add);//下传到左右子节点
		tree[u].add=0;//标记清空
	}

区间加

区间加和区间查询差不多,注意:在任意时刻访问左右子节点都要下传标记

void update(int u,int x,int y,int v){//将区间[x,y]都加上v
		int l=tree[u].l,r=tree[u].r;//将对应区间取出
		if(l>y||r<x)return;//不包括要修改的区间
		if(x<=l&&r<=y){addline(u,v);return ;}//完全包含于要修改的区间,直接打标记
		pushdown(u);//下传标记
		update(u*2,x,y,v);update(u*2+1,x,y,v);//修改左右节点
		pushup(u);//更新节点
	}

区间查询

int find(int u,int x,int y){//求区间[x,y]的最大值
		int l=tree[u].l,r=tree[u].r;
		if(l>y||r<x)return -0x3f3f3f3f;//完全不包含
		if(x<=l&&r<=y)return tree[u].mx;//完全包含于所求区间
		pushdown(u);//下传标记
		return max(find(u*2,x,y),find(u*2+1,x,y));//左右节点访问的最大值
	}

你可能感兴趣的:(线段树/平衡树,线段树,数据结构,算法)