在上一篇题解。我们详细讲解了单点修改,区间查询的线段树。在这篇题解我们将要讲解区间修改,区间查询的线段树。
我们发现虽然我们可以做到在 O ( l o g n ) O(log_{n}) O(logn)的时间内做到单点修改,但我们如果将一个区间修改,我们发现时间复杂度为 O ( n l o g n ) O(nlog_{n}) O(nlogn),比暴力还慢。那我们只能想一些其他方法了。
我们先从线段树的结构入手:
还是这张图,红线为访问区间 [ 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));//左右节点访问的最大值
}