一般我们写线段树等数据结构的时候都会写需要下防的标记,虽然不是很复杂,但是在面对持久化数据结构或树套树的时候,我们就需要一个技巧,就是标记永久化。。
我扔http://acm.hdu.edu.cn/showproblem.php?pid=4348
这是到经典的主席树练习题,然而再区间修改的时候要用到标记永久化。
标记永久化怎么做呢?
首先标记永久化肯定是不下传,我们只需在询问的过程中计算每个遇到的结点对询问的影响(贡献)就可以了,子节点的影响在需要的时候计算就好了,怎么弄呢?
首先我们假设标记是add,sum值表示的这个区间内所有书共同加上的值,sum表示这个区间内处理add之外其他值的和,总的来说就是表示这个区间的和(修改后的)。
看看代码吧
#include
#define ll long long
using namespace std;
const int N = 100007;
int ad[N*30],n,m,ch[N*30][2],root[N],tot,sz;
ll sum[N*30];
void update(int rt){
sum[rt]=sum[ch[rt][0]]+sum[ch[rt][1]];
}
int build(int rt,int l,int r){//日常建树
rt=++tot;
if(l==r) {scanf("%lld",&sum[rt]);return rt;}
int mid=(l+r)>>1;
ch[rt][0]=build(rt,l,mid);
ch[rt][1]=build(rt,mid+1,r);
update(rt);
return rt;
}
int add(int rt,int l,int r,int L,int R,int x){
int rtt=++tot;
ch[rtt][0]=ch[rt][0];ch[rtt][1]=ch[rt][1]; //先附值,因为后面可能不一定附得了
ad[rtt]=ad[rt];//继承标记
sum[rtt]=sum[rt];//继承值
sum[rtt]+=(ll)x*(min(R,r)-max(L,l)+1);//计算这个标记对sum值的影响,就是这个区间要修改的长度*要修改的值
if(L<=l&&r<=R){
ad[rtt]+=x;//打上标记
return rtt;
}
int mid=(l+r)>>1;
if(L<=mid) ch[rtt][0]=add(ch[rtt][0],l,mid,L,R,x);//正常的主席树
if(R>mid) ch[rtt][1]=add(ch[rtt][1],mid+1,r,L,R,x);
return rtt;
}
ll query(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R){
return sum[rt];//因为sum是表示已经更新过的值,所以可以直接返回
}
ll ret=(ll)ad[rt]*(min(R,r)-max(l,L)+1),mid=(l+r)>>1;//计算标记对答案的贡献,就是和上面的一样。
if(L<=mid) ret+=query(ch[rt][0],l,mid,L,R);//计算左右区间的贡献
if(R>mid) ret+=query(ch[rt][1],mid+1,r,L,R);
return ret;
}
void init(){
tot=0,sz=0;
memset(sum,0,sizeof(sum));memset(ad,0,sizeof(ad));memset(root,0,sizeof(root));
}
int main(){
while(~scanf("%d%d",&n,&m)){
init();
root[0]=build(0,1,n);
for(;m--;){
int x,y,z;
char c;
scanf(" %c",&c);
if(c=='C'){
scanf("%d%d%d",&x,&y,&z);
root[sz+1]=add(root[sz],1,n,x,y,z);
sz++;
}
else
if(c=='Q'){
scanf("%d%d",&x,&y);
printf("%lld\n",query(root[sz],1,n,x,y));
}
else
if(c=='H'){
scanf("%d%d%d",&x,&y,&z);
printf("%lld\n",query(root[z],1,n,x,y));
}
else
if(c=='B'){
scanf("%d",&sz);
}
}
}
return 0;
}
日常AC
差不多就是这样了吧,其实挺好理解的,主要是sum的意义要记清楚,然后就可以了。
真棒!!!
参考资料:《信息学奥赛一本通· 提高篇》