浅谈标记永久化

一般我们写线段树等数据结构的时候都会写需要下防的标记,虽然不是很复杂,但是在面对持久化数据结构或树套树的时候,我们就需要一个技巧,就是标记永久化。。

我扔http://acm.hdu.edu.cn/showproblem.php?pid=4348

这是到经典的主席树练习题,然而再区间修改的时候要用到标记永久化。

标记永久化怎么做呢?

首先标记永久化肯定是不下传,我们只需在询问的过程中计算每个遇到的结点对询问的影响(贡献)就可以了,子节点的影响在需要的时候计算就好了,怎么弄呢?
首先我们假设标记是add,sum值表示的这个区间内所有书共同加上的值,sum表示这个区间内处理add之外其他值的和,总的来说就是表示这个区间的和(修改后的)。

看看代码吧

#include<bits/stdc++.h>
#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的意义要记清楚,然后就可以了。
真棒!!!

参考资料:《信息学奥赛一本通· 提高篇》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值