成段更新的重点是延迟更新,以区间[1,3]为例说明。
注意,此例中“更新”为修改元素的值为a,“查询”为求区间中所有元素之和。
建立二叉树如图示:
每一个圆圈代表一个结点,圆内数字分别为结点标号和所对应区间,[i]表示只含一个数,只出现在叶节点中。
当 要更新区间[1,2]中所有元素时,对应上图即要更新结点4,5。一种方法是依次访问结点4和5并更新,但这样时间和空间开销都较大,当要修改的区间的长 度较长时尤甚。于是可以采取“延迟更新”,即将更新信息储存在这段区间对应的结点处,此例中 [1,2]对应的区间是结点2,给结点一个属性tag用以 记录这个更新信息a,原来tag初始化为0,此时令tag = a。这样做实际上是将整一个区间[1,2]看做一个“点”,即应用单点更新。这样做的好处 是不需要更新结点4和5。而事实上,在这一步中我们也只需要知道整一个区间[1,2]的情况就足矣,如果以后每次查询都是查询整一个区间[1,2],又何 须分别更新结点1和2?但是,存在一些情况,如查询区间[2,3],由于上一次我们只更新了整一个区间[1,2]所对应的结点2,而没有更新结点2,此时 查询[2,3]会导致错误。此时tag起作用。要查询到结点5必须经过结点2,经过每一个结点均检查标识tag,若tag非0,表明上次没有往下执行更 新,于是执行往下更新的操作:更新1和2,然后对结点2的tag置0。
延迟更新的方法就是:当前父结点存储更新信息而不往下更新以减少时间可空间开销,当下次用到时再局部更新。
#include<cstdio> #include <cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; #define lson l,m,rt<<1 #define rson r,m+1,rt<<1|1 const int maxn=55555; int sum[maxn<<2]; int C[maxn<<2];//乘法延时标记数组 初始化为1 void down(int l,int r,int rt) { if(C[rt]!=1) { sum[rt<<1]=sum[rt<<1]*C[rt]; sum[rt<<1|1]=sum[rt<<1|1]*C[rt]; C[rt<<1|1]=C[rt<<1]=C[rt]; C[rt]=1; } } void Pushup(int rt) { sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt) { if(l==r) { scanf("%d",&sum[rt]); return; } int m=(l+r)>>1; build(l,m,rt<<1); bulid(m+1,r,rt<<1|1); Pushup(rt); } void update(int p,int add,int l,int r,int rt)//单节点跟新不需要用到延时标记数组 { if(l==r){ sum[rt]=sum[rt]+add; return; } int m=(l+r)>>1; if(p<=m) update(p,add,l,m,rt<<1); else update(p,add,m+1,r,rt<<1|1); Pushup(rt); } int query(int L,int R,int l,int r,int rt) //区间信息查询也不需要用到延时数组 只有修改区间的时候使用到延时数组 //但是需要使用到down数组 看这个区间的数值是否需要跟新 保证结果的正确性 { if(L<=l&&r<=R) return sum[rt]; down(l,r,rt); int m=(l+r)>>1; int ans=0; if(L<=m) ans=ans+query(L,R,l,m,rt<<1); if(R>m) ans=ans+query(L,R,m+1,r,rt<<1|1); return ans; } void modify(int L,int R,int l,int r,int rt,int c) { if(L<=l&&r<=R) { sum[rt]=sum[rt]*c; C[rt]=c;//在访问之前sum的数据已经跟新了 就意味着C数组的值为0 return; } down(l,r,rt); int m=(l+r)>>1; if(L<=m) modify( L, R,l,m,rt<<1,c); if(R>m) modify( L, R,m+1,r,rt<<1|1,c); Pushup(rt); }