您的位置:首页 > 其它

线段树区间更新(以POJ 3468为例)

2016-12-22 22:05 162 查看
在了解单点更新的线段树的前提下,再继续理解区间更新的线段树。

区间更新是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是O(lgn)。为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。

延迟标记:每个节点新增加一个标记,记录该节点是否进行了某种修改。将指定更新区间在树上找到对应节点后,给这些节点打上标记。在查询时,如果当前节点有标记,就给它的子节点打上该标记,并删除当前节点的标记。

延迟标记除了能够记录某个节点的是否进行修改外,通常也记录修改的情况,方便在查询时直接能够得到修改的结果。

struct Node{
ll l,r;
ll sum,add;
}


对于区间更新的线段树,通常有四个功能:

build:左右递归建树,初始化每个节点sum值。同时将每个节点的延迟标记设置为0。

pushDown:将父节点的延迟标记传递给子节点(如果题意需要,还要将父节点的值传给子节点),同时将父节点的延迟标记清空。

query:对指定区间查询,左右子树递归查询。查询时如果父节点有延迟标记,需要向下传递。

update:对指定区间更新,左右子树递归更新。当更新到指定区间时,加上父节点的延迟标记(如果题意需要,还要进行其他操作),返回。如果没有更新到指定区间,就继续更新,如果过程中有节点有延迟标记,需要向下传递。最后回溯更新父节点的值。

注意:

update时,只有指定区间会有延迟标记,并且此时指定区间和它的父区间的sum已经更改,它的子区间和其他区间没有延迟标记和修改值。

query时,延迟标记会传至子区间,并且同时修改子区间的值,直到找到指定区间。此时,延迟标记在指定区间上,指定区间的子区间的sum值没有修改。

对于POJ 3468:

在pushDown和update时,子区间需要加上父区间传过来的值*区间元素数;

(具体操作见代码)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a
;
char opr[10];
struct Node{ ll l,r; ll sum,add; }tree[4*N];
void build(ll i,ll l,ll r){
tree[i].l=l,tree[i].r=r;
tree[i].add=0;
if(l==r){//左边=右边,单点建立
tree[i].sum=a[l];
return;
}
ll tmp=i<<1;
ll mid=(l+r)>>1;
build(tmp,l,mid);//左子树
build(tmp+1,mid+1,r);//右子树
tree[i].sum=tree[tmp].sum+tree[tmp+1].sum;//回溯得到区间sum值
}
void pushDown(ll x){
ll tmp=x<<1;
tree[tmp].add+=tree[x].add;//左子树得到父节点标记
tree[tmp+1].add+=tree[x].add;//右子树得到父节点标记

//如果区间内每个元素加上一个值,那么这个区间会加上当前区间包含指定区间元素个数*待加元素的值
tree[tmp].sum+=tree[x].add*(tree[tmp].r-tree[tmp].l+1);
tree[tmp+1].sum+=tree[x].add*(tree[tmp+1].r-tree[tmp+1].l+1);
tree[x].add=0;//清空父节点标记
}
ll query(ll i,ll l,ll r){

if(tree[i].l>r||tree[i].r<l){//超出范围
return 0;
}
if(tree[i].l>=l&&tree[i].r<=r){//刚好在落到求得的区间
return tree[i].sum;
}
//如果查询时发现当前区间不为目标区间,但是有延迟标记时,将延迟标记传递
if(tree[i].add) pushDown(i);

ll tmp=i<<1;

//左右子树分别查询,将查询结果相加
//如果某一子树超出范围,会在第一个条件处return 0
return query(tmp,l,r)+query(tmp+1,l,r);

}
void update(ll i,ll l,ll r,ll s){
if(tree[i].l>r||tree[i].r<l){//超出范围
return ;
}

//在指定区间的范围内,该点加上sum与延迟标记.此时包含该点的父区间均更新完毕
//>=、<=是考虑到区间位于不同子树的情况

if(tree[i].l>=l&&tree[i].r<=r){
tree[i].add+=s;
tree[i].sum+=s*(tree[i].r-tree[i].l+1);
return;
}
//如果之前已经有延迟标记,需要将之前的标记处理
if(tree[i].add) pushDown(i);
ll tmp=i<<1;

//更新左右子树,如果某个子树超出了所更新区间的范围,会在第一个条件处return

update(tmp,l,r,s);
update(tmp+1,l,r,s);

tree[i].sum=tree[tmp].sum+tree[tmp+1].sum;//回溯更新父节点的值
}

int main(){
int n,q;
while(~scanf("%d %d",&n,&q)){
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
build(1,1,n);
ll l,r,s;
while(q--){
scanf("%s",opr);
if(opr[0]=='Q'){
scanf("%lld %lld",&l,&r);
cout<<query(1,l,r)<<endl;

}
else if(opr[0]=='C'){
scanf("%lld %lld %lld",&l,&r,&s);
update(1,l,r,s);
}
}
}
}
/*
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

5 10
1 2 3 4 5
C 3 5 2
Q 3 5

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: