线段树之区间更新
线段树系列上一篇文章讲了基础的线段树的建树,单点更新,区间查询。那这篇文章主要讲线段树的区间更新,也就是延迟更新。其实延迟更新的本质和单点更新差不多,只不过差别在于单点更新每次都递归到底,但是区间更新则是做一个延迟标记,等到下次更新或查询的时候再去判断是不是往下更新。
为什么这样?答案是显然的。线段树的查询和单点更新的时间复杂度是O(logn)的,如果我每次都只是更新一个节点,再去询问,那么复杂度是O(nlogn),这个复杂度是可以接受的,但是如果每次操作我都是更新一个区间,还是用单点更新去做,那么复杂度是O(n^2logn),这个复杂度是很大的,一般来说,在ACM竞赛中是很难接受的或者说是直接摒弃的。所以我们要想办法去降低复杂度。于是我们就有了区间更新。
以下我们以给某个区间增加x为例讲解:
假设我要更新区间[a,b],那我开始由根节点开始递归查找区间,如果发现区间[l,r]完全包含在区间[a,b]里,我就在这停止,不在继续更新下去,并在这个节点做标记记录x,并更新这个节点的sum值。如果下次查询的时候,碰见标记就开始更新左右儿子,并把自己的标记消除,给儿子做标记,以此类推,直到找到符合查找的区间为止。
这样为什么会快,当节点数很多的时候,如果我每次都把整个操作区间更新到底就会很浪费时间,通过标记,我可以更新到某一层就停止,不必更新到底,这样当然就很节省时间了。
我们就以这个题意编写代码讲解:
题意:在一组数中执行两种操作:
(1) "C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
(2)"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.
const int maxn=10005;//假设点的个数
struct seg_tree//为线段树定义一个结构体
{
int sum,seg;//sum表示区间和,seg作为标记
}tree[4*maxn];
//更新父亲节点操作
void pushup(int root)
{
tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
return ;
}
//更新左右儿子操作
void pushdown(int root,int l,int r)
{
int mid=(l+r)>>1;
//更新左儿子
tree[root<<1].seg+=tree[root].seg;
tree[root<<1].sum+=(mid-l+1)*tree[root].seg;
//更新右儿子
tree[root<<1|1].seg+=tree[root].seg;
tree[root<<1|1].sum+=(r-(mid+1)+1)*tree[root].seg;
//消除父亲节点的标记
tree[root].seg=0;
return ;
}
//建树操作
void build_tree(int root,int r,int l)
{
tree[root].seg=0;
if(l==r)
{
tree[root].sum=A[l];
return ;
}
int mid=(r+l)>>1;
build_tree(root<<1,l,mid);
build_tree(root<<1|1,mid+1,r);
pushup(root);
}
//更新操作
void update(int root,int l,int r,int L,int R,int val )//L,R表示更新的区间的端点,val表示要加的值
{
if(L<=l&&r<=R) //如果被操作区间完全包含,就不用往下更新了
{
tree[root].sum+=(r-l+1)*val;
tree[root].seg+=val;
return ;
}
if(tree[root].seg!=0) { pushdown(root,l,r);}//延迟更新
int mid=(r+l)>>1;
if(L<=mid)
update(root<<1,l,mid,L,R,val);
if(mid<R)
update(root<<1|1,mid+1,r,L,R,val);
pushup(root);
}
//查询操作
int query(int root,int l,int r,int L,int R)//L,R表示查询的区间的端点
{
if(L<=l&&r<=R)
{
return tree[root].sum;
}
if(tree[root].seg!=0)//如果有标记就需要往下更新
pushdown(root,l,r);
int mid=(r+l)>>1,sum=0;
if(L<=mid)
sum+=query(root<<1,l,mid,L,R);
if(mid<R)
sum+=query(root<<1|1,mid+1,r,L,R);
return sum;
}