【HDU - 4348】To the moon (主席树 标记永久化区间更新)

传送门

主席树的修改一般是只涉及单点修改的,因为区间操作如果我们pushdown的话,相当于又涉及了两个孩子节点的修改,一直递归下去,这样空间要去就和建n棵线段树差不多一个量级了。

我们可以把懒标记永久打在节点上,不下传,只在pushup和查询的时候用上。
具体来说,pushup的时候除了合并左右子节点,再考虑一下根节点区间的懒标记的影响。
查询的时候,查询路径上经过节点的懒标记都要合并起来,一路记录下去,最终在查询区间进行计算。

涉及回退历史版本时,要注意是否是时间上的回溯。

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){while(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){while(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,m;
ll tag[maxn*40],tree[maxn*40];
int lc[maxn*40],rc[maxn*40],tot;
int root[maxn];
//给定序列 访问历史版本 区间查询  区间更新 
//主席树的区间修改 标记永久化
inline void pushup(int rt,int l,int r)
{
    //对于区间[l,r]而言 tag[rt]并没有传递更新给lc rc 所以除了lc rc的和还得加上tag[rt]的贡献 
    tree[rt]=tree[lc[rt]] + tree[rc[rt]] + tag[rt]*(r-l+1);
}
inline void build(int &rt,int l,int r)
{
    rt=++tot;
    tag[rt]=0;
    lc[rt]=0,rc[rt]=0;
    if(l==r) 
    {
        scanf("%lld",&tree[rt]);
        return ;
    }
    int mid=l+r>>1;
    build(lc[rt],l,mid);
    build(rc[rt],mid+1,r);
    pushup(rt,l,r);
}
inline void upd(int &rt1,int rt2,int l,int r,int vl,int vr,int v)
{
    if(r<vl || l>vr) return ;
    rt1=++tot;
    tree[rt1]=tree[rt2];
    lc[rt1]=lc[rt2],rc[rt1]=rc[rt2];
    tag[rt1]=tag[rt2];//tag也得继承
    if(vl<=l && r<=vr) 
    {
        tag[rt1]+=v;
        tree[rt1]+=1ll*(r-l+1)*v;
        return ;
    }
    int mid=l+r>>1;
    upd(lc[rt1],lc[rt2],l,mid,vl,vr,v);
    upd(rc[rt1],rc[rt2],mid+1,r,vl,vr,v);
    pushup(rt1,l,r);
}
inline ll qry(int rt,int l,int r,int vl,int vr,ll add)
{
    if(!rt) return 0;
    if(vl<=l && r<=vr) 
    {
        return tree[rt]+add*(r-l+1);
    }
    int mid=l+r>>1;
    if(vr<=mid) return qry(lc[rt],l,mid,vl,vr,add+tag[rt]);//一路把遇到的懒标记都合并起来
    else if(vl>mid) return qry(rc[rt],mid+1,r,vl,vr,add+tag[rt]);
    return qry(lc[rt],l,mid,vl,vr,add+tag[rt]) + qry(rc[rt],mid+1,r,vl,vr,add+tag[rt]);
}
int main()
{
    while(~scanf("%d %d",&n,&m))
    {
        tot=0;
        build(root[0],1,n);//初始的时刻是0
        int clk=0;
        char op[3];
        int l,r,t;
        while(m--)
        {
            scanf("%s",op);
            if(op[0]=='C')//区间更新
            {
                scanf("%d %d %d",&l,&r,&t);
                upd(root[clk+1],root[clk],1,n,l,r,t);
                ++clk;
            }
            else if(op[0]=='Q') //查询当前版本区间和
            {
                scanf("%d %d",&l,&r);
                printf("%lld\n",qry(root[clk],1,n,l,r,0));
            }
            else if(op[0]=='H') //查询历史版本区间和
            {
                scanf("%d %d %d",&l,&r,&t);
                printf("%lld\n",qry(root[t],1,n,l,r,0));
            }
            else //回退到t版本 这里不能写成root[clk]=root[t] 因为题目要求是回到第t时刻
            //回去之后 下一时刻是t+1时刻 而不是clk+1时刻 
            {
                scanf("%d",&t);
                clk=t;
            }
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值