hdu4348 To the moon(可持久化线段树+标记永久化)

题意:

给定长度为n的序列,m次操作,一开始的时间戳为0
操作有四种:
(1)C l r d:区间[l,r]中的数都加1,同时当前的时间戳加1 。
(2)Q l r:查询当前时间戳区间[l,r]中所有数的和 。
(3)H l r t:查询时间戳t区间[l,r]的和 。
(4)B t:将当前时间戳置为t 。即回到过去t时刻。

数据范围:n,m<=1e5

解法:

显然可持久化,
但是涉及区间修改操作,所以要将标记永久化,而不能pushdown,因为pushdown需要新建的区间点太多。

标记永久化就是给节点打上一个区间修改标记tag,但是不下传,

当查询的区间[st,ed]完全被当前区间[l,r]包含时,tag可以用于统计答案
和平常线段树不太一样的地方:
1.因为[st,ed]需要完全被[l,r]包含,所以线段树的写法和平常不太一样,是拆区间的写法,
线段树常见写法:

if(st<=mid)update(st,ed,val,l,mid,node*2);
if(ed>mid)update(st,ed,val,mid+1,r,node*2+1);

标记永久化必须用的写法:

if(ed<=mid)update(st,ed,val,l,mid,node*2);
else if(st>mid)update(st,ed,val,mid+1,r,node*2+1);
else update(st,mid,val,l,mid,node*2),update(mid+1,ed,val,mid+1,r,node*2+1);

因为要时刻保证[st,ed]被[l,r]完全包含

2.因为每个区间节点都有一个tag,不同区间节点之间的tag是相互独立、没有联系的,
所以pushup的写法也不一样,
例如现在要维护区间和,操作为区间加法,那么pushup是这样的:

void pushup(int k,int l,int r){//k是节点编号
    sum[k]=sum[lc[k]]+sum[rc[k]]+add[k]*(r-l+1);
}

每次的add标记都要计算进去,因为和普通laz标记不一样,没有被下传然后清空

-----分割线-----

暂时就这么多

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
int lc[maxm*40],rc[maxm*40],sum[maxm*40],add[maxm*40];
int rt[maxm],tot;
int now;
//
int a[maxm];
int n,m;
void init(){
    tot=0;
    now=0;
}
void pushup(int k,int l,int r){
    sum[k]=sum[lc[k]]+sum[rc[k]]+add[k]*(r-l+1);
}
void build(int l,int r,int &k){
    k=++tot;
    lc[k]=rc[k]=add[k]=0;
    if(l==r){
        sum[k]=a[l];
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,lc[k]);
    build(mid+1,r,rc[k]);
    pushup(k,l,r);
}
void update(int st,int ed,int val,int l,int r,int last,int &k){
    k=++tot;
    //
    lc[k]=lc[last],rc[k]=rc[last];
    sum[k]=sum[last],add[k]=add[last];
    //
    if(st==l&&ed==r){
        sum[k]+=val*(ed-st+1);
        add[k]+=val;
        return ;
    }
    int mid=(l+r)/2;
    if(ed<=mid)update(st,ed,val,l,mid,lc[last],lc[k]);
    else if(st>mid)update(st,ed,val,mid+1,r,rc[last],rc[k]);
    else update(st,mid,val,l,mid,lc[last],lc[k]),update(mid+1,ed,val,mid+1,r,rc[last],rc[k]);
    pushup(k,l,r);
}
int ask(int st,int ed,int l,int r,int k){
    if(!k)return 0;
    if(st==l&&ed==r)return sum[k];
    int mid=(l+r)/2;
    int ans=add[k]*(ed-st+1);//这时候[st,ed]一定被[l,r]全包含
    if(ed<=mid){
        ans+=ask(st,ed,l,mid,lc[k]);
    }else if(st>mid){
        ans+=ask(st,ed,mid+1,r,rc[k]);
    }else{
        ans+=ask(st,mid,l,mid,lc[k])+ask(mid+1,ed,mid+1,r,rc[k]);
    }
    return ans;
}
signed main(){
    ios::sync_with_stdio(0);
    while(cin>>n>>m){
        init();
        for(int i=1;i<=n;i++)cin>>a[i];
        build(1,n,rt[now]);
        while(m--){
            char op;cin>>op;
            if(op=='C'){
                int l,r,d;cin>>l>>r>>d;
                now++;
                update(l,r,d,1,n,rt[now-1],rt[now]);
            }else if(op=='Q'){
                int l,r;cin>>l>>r;
                int ans=ask(l,r,1,n,rt[now]);
                cout<<ans<<endl;
            }else if(op=='H'){
                int l,r,t;cin>>l>>r>>t;
                int ans=ask(l,r,1,n,rt[t]);
                cout<<ans<<endl;
            }else if(op=='B'){
                int x;cin>>x;
                tot=rt[x+1]-1;
                now=x;
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值