在需要对线段树进行区间修改(如给整个区间加减某一值,或全部改为某一值)时,若反复使用之前的单点修改,那么算法的时间复杂度将达到一个恐怖的级别(单次修改nlogn,超出了不用线段树的时间复杂度),因此要引入“标记”思想。
先看仅需区间修改与单点查询的解决思路:
对[1,n]的[l,r]子区间进行修改:整体加k
依照已有的线段树,将[l,r]划分为m个子区间(所有划分方法中取m最小情况),对每个区间设置一个标记addnum[i],将这些子区间的标记值全部加k
在进行单点查询时,从根节点往下一直查询到该叶子节点,将路上的addnum值累加,再加上叶子节点本身对应的值,即为修改过后该节点的值
为了方便起见,可以将叶子节点原本值也计入addnum中
修改代码: 将[x,y]全部加k
void updata_array(int o,int l,int r,int x,int y,int k)
{
if(x<=l&&y>=r){addsum[o]+=k;return;}
int m=(l+r)>>1; //用>>计算比/更高效
if(x<=m)updata_array(o*2,l,m,x,y,k);
if(y>m)updata_array(o*2+1,m+1,r,x,y,k);
}
查询代码:查询p点现在的值
int Query(int o,int l,int r,int p)
{
if(l==r)return addsum[o];
int m=(l+r)>>1;
if(p<=m) return Query(o*2,l,m,p)+addsum[o];
else return Query(o*2+1,m+1,r,p)+addsum[o];
}
但面临区间修改与区间查询时,上述方法同样不再适用。我们需要继续对“标记”进行处理。常规方法有标记下传和标记永久化,这个标记被称为“Lazy标记”。
这里先写标记下传。
涉及到区间查询,我们需要同时维护两个甚至更多的值,一是记录修改信息(如addsum),一是记录查询信息,如(sum,maxnum)。
这里先以修改方式为区间整体加值,查询内容为区间和为例。
在未经过修改的情况下,一个节点o的sum值恒为sum[2o]+sum[2o+1]
在经过修改后,对一个节点o,对应区间为[l,r],若此节点的所有祖先的addsum值都为0,而该节点的addsum值刚被修改为k,sum值为s,则该节点的sum值可由sum+k*(r-l+1)计算更新。
当需要查询这一节点的sum值时,直接返回更新后的sum即可,该节点的子节点情况如何,不影响正确的sum值。
若一个节点的本身及其祖先标记都为0,则其仍然遵循sum[o]=sum[2o]+sum[2o+1]
若该节点的祖先节点有addsum值不为0,则此时需要将祖先节点的标记下传,直至到这一节点的祖先的标记都为0。
将一个节点的标记下传方式如下:
①将该节点的addsum值累加到左子节点,并将左子节点的sum值依据更新值累加。
②将该节点的addsum值累加到右子节点,并将右子节点的sum值依据更新值累加。
③将该节点的addsum值清空
下传代码如下:
void Add_point(int o,int l,int r,int k)
{
addsum[o]+=k;
sum[o]+=(r-l+1)*k;
}
void pushdown(int o,int l,int r)
{
if(addsum[o]==0)return;
int m=(l+r)>>1;
Add_point(o*2,l,m,addsum[o]);
Add_point(o*2+1,m+1,r,addsum[o]);
addsum[o]=0;
}
其中pushdown(o,l,r)为对一个节点进行标记下传
有了上面的代码,便可根据之前”把祖先节点的标记全部下传“的思路写出查询代码
int Query(int o,int l,int r,int x,int y)
{
if(x<=l&&y>=r)return sum[o];
pushdown(o,l,r);
int m=(l+r)>>1,res=0;
if(x<=m)res+=Query(o*2,l,m,x,y);
if(y>m)res+=Query(o*2+1,m+1,r,x,y);
return res;
}
同理,进行区间修改时,同样需要在查询子节点之前,把父节点的标记下传。
修改代码如下: (将区间[x,y]加上k)
void updata_array(int o,int l,int r,int x,int y,int k)
{
if(x<=l&&y>=r){Add_point(o,l,r,k);return;}
pushdown(o,l,r);
int m=(l+r)>>1;
if(x<=m)updata_array(o*2,l,m,x,y,k);
if(y>m)updata_array(o*2+1,m+1,r,x,y,k);
sum[o]=sum[o*2]+sum[o*2+1];
}
下一节将复习“标记永久化”