【复习】线段树(2)区间修改与lazy标记——标记下传

在需要对线段树进行区间修改(如给整个区间加减某一值,或全部改为某一值)时,若反复使用之前的单点修改,那么算法的时间复杂度将达到一个恐怖的级别(单次修改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];
}

下一节将复习“标记永久化”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线段树是一种二叉树形数据结构,适用于动态的区间查询和区间修改。下面给出线段树区间求和和区间修改的实现方法。 1.区间求和 线段树区间求和操作可以用递归的方式进行。对于每一个非叶节点,维护一个区间的和,左右儿子分别维护左半区间和右半区间的和。查询区间的时候,用二分的方式进行递归,直到查询的区间和当前节点所维护的区间有交集为止,将左右儿子的和相加即可得到查询区间的和。 2.区间修改 线段树区间修改可以用懒标记的方式来实现。对于每一个节点,维护一个懒标记,表示这个节点的区间是否被修改过。在对一个区间进行修改时,将懒标记打上,然后递归下去更新左右儿子的区间和,最后将懒标记清空。查询的时候,如果一个节点的懒标记不为空,就先下推懒标记,再递归查询子区间。 参考代码: 区间求和: ```python class SegmentTree: def __init__(self, n): self.n = n self.tree = * (n*4) def build(self, node, left, right, nums): if left == right: self.tree[node] = nums[left] return mid = (left + right) >> 1 self.build(node*2, left, mid, nums) self.build(node*2+1, mid+1, right, nums) self.tree[node] = self.tree[node*2] + self.tree[node*2+1] def query(self, node, left, right, qleft, qright): if left > qright or right < qleft: return 0 if left >= qleft and right <= qright: return self.tree[node] mid = (left + right) >> 1 return self.query(node*2, left, mid, qleft, qright) + self.query(node*2+1, mid+1, right, qleft, qright) def update(self, node, left, right, uleft, uright, val): if left > uright or right < uleft: return if left >= uleft and right <= uright: self.tree[node] += val * (right - left + 1) if left != right: self.lazy[node*2] += val self.lazy[node*2+1] += val return mid = (left + right) >> 1 self.update(node*2, left, mid, uleft, uright, val) self.update(node*2+1, mid+1, right, uleft, uright, val) self.tree[node] = self.tree[node*2] + self.tree[node*2+1] ``` 区间修改: ```python class SegmentTree: def __init__(self, n): self.n = n self.tree = * (n*4) self.lazy = * (n*4) def build(self, node, left, right, nums): if left == right: self.tree[node] = nums[left] return mid = (left + right) >> 1 self.build(node*2, left, mid, nums) self.build(node*2+1, mid+1, right, nums) self.tree[node] = self.tree[node*2] + self.tree[node*2+1] def query(self, node, left, right, qleft, qright): if left > qright or right < qleft: return 0 if left >= qleft and right <= qright: return self.tree[node] mid = (left + right) >> 1 self.pushdown(node, mid-left+1, right-mid) return self.query(node*2, left, mid, qleft, qright) + self.query(node*2+1, mid+1, right, qleft, qright) def update(self, node, left, right, uleft, uright, val): if left > uright or right < uleft: return if left >= uleft and right <= uright: self.tree[node] += val * (right - left + 1) if left != right: self.lazy[node*2] += val self.lazy[node*2+1] += val return mid = (left + right) >> 1 self.pushdown(node, mid-left+1, right-mid) self.update(node*2, left, mid, uleft, uright, val) self.update(node*2+1, mid+1, right, uleft, uright, val) self.tree[node] = self.tree[node*2] + self.tree[node*2+1] def pushdown(self, node, leftlen, rightlen): if self.lazy[node]: self.tree[node*2] += self.lazy[node] * leftlen self.tree[node*2+1] += self.lazy[node] * rightlen self.lazy[node*2] += self.lazy[node] self.lazy[node*2+1] += self.lazy[node] self.lazy[node] = 0 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值