可持久化线段树

之前在 codeforces 做过一道题目,算是第一次接触这东西吧。

题目的要求是,对一个n行n列的格子进行维护。单点更新或者区间更新。还有一个是返回第 k 次更新的状态。

直接就懵逼了。0.0   这是个什么鬼。

如果每次操作我都要记录状态的话,不用想,肯定内存会爆炸的。那我如果遇到该回去的操作时再原路返回,肯定时间会爆炸的。好吧,我知道我肯定要学一种新的数据结构了。

不过对这个题目后来我想了想,如果我不是在线做,而是离线来处理所有的查询呢?我们预先记录所有的返回操作。

这样是否可行呢?

下面主要来说一下在线的做法。

据说这个在线的做法有个很奇怪的名字:主席树。

伤心的是在BZOJ上找不到有关主席树的题目。因为只有 VIP 才可以看到。


这段内容其实在线段树的基础上很好理解。

我们一开始维护一个线段树。每次操作之后产生一个新树,不过这个树是在原来的基础上延伸出来的。

也就是对所操作的区间到根节点建立一个新的树,这个树和原来的树是连接在一起的。那么对于时间戳我们如何记录?每个延伸出来的节点,都是一个时间戳。一开始的树根节点时间为  1   ,下一次操作从原来的树延伸出所操作的区间,延伸出来的所有节点的根节点时间为 2,然后就没有了。0.0


初步理解是这个样子,日后若是有很好的表达,再来更新。0.0


贴下HDU 4348 的代码。


#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100000+7;
int root[N],tot;
int Lson[N*30],Rson[N*30],add[N*30];
ll sum[N*30];
int n,m;
inline int bulid(int L,int R)
{
    int rt=tot++;
    add[rt]=0;
    if (L==R)
    {
        scanf("%lld",&sum[rt]);
        return rt;
    }

    int mid=(L+R)>>1;
    Lson[rt]=bulid(L,mid);
    Rson[rt]=bulid(mid+1,R);

    sum[rt]=sum[Lson[rt]]+sum[Rson[rt]];

    return rt;
}

inline int update(int rt,int L,int R,int x,int LL,int RR)
{
    int k=tot++;
    Lson[k]=Lson[rt];
    Rson[k]=Rson[rt];
    add[k]=add[rt];
    sum[k]=sum[rt];

    sum[k]+=(ll)x*(R-L+1);

    if (LL==L && RR==R)
    {
        add[k]+=x;
        return k;
    }

    int mid=(LL+RR)>>1;
    if (R<=mid) Lson[k]=update(Lson[k],L,R,x,LL,mid);
    else if (L>mid) Rson[k]=update(Rson[k],L,R,x,mid+1,RR);
    else
    {
        Lson[k]=update(Lson[k],L,mid,x,LL,mid);
        Rson[k]=update(Rson[k],mid+1,R,x,mid+1,RR);
    }

    return k;
}

inline ll query(int rt,int L,int R,int LL,int RR)
{
    if (L==LL && R==RR) return sum[rt];

    int mid=(LL+RR)>>1;

    ll ret=(ll)add[rt]*(R-L+1);

    if (R<=mid) return ret+query(Lson[rt],L,R,LL,mid);
    else if (L>mid) return ret+query(Rson[rt],L,R,mid+1,RR);
    else return ret+query(Lson[rt],L,mid,LL,mid)+query(Rson[rt],mid+1,R,mid+1,RR);
}

int main()
{
    int x,L,R;
    int now;
    char ch[3];

    bool f=false;

    while (~scanf("%d%d",&n,&m))
    {
        if (f) puts("");
        else f=true;

        tot=0;
        root[0]=bulid(1,n);
        now=0;

        while (m--)
        {
            scanf("%s",ch);
            if (ch[0]=='C')
            {
                scanf("%d%d%d",&L,&R,&x);
                now++;
                root[now]=update(root[now-1],L,R,x,1,n);
            }
            else if (ch[0]=='Q')
            {
                scanf("%d%d",&L,&R);
                printf("%lld\n",query(root[now],L,R,1,n));
            }
            else if (ch[0]=='H')
            {
                scanf("%d%d%d",&L,&R,&x);
                printf("%lld\n",query(root[x],L,R,1,n));
            }
            else if (ch[0]=='B')
            {
                scanf("%d",&now);
            }
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值