可持久化线段树1

可持久化线段树 1


题目描述

洛谷P3919 可持久化线段树 1(可持久化数组)


核心思路

可持久化数组是一种可以支持回退,访问之前版本的数组, 是一些其他可持久化数据结构的基石(例如可持久化并查集)。

这个题目我们可以使用到主席树,可以先看这道题可持久化线段树2,它是使用主席树来维护权值线段树。

但是对于这道题,我们就不用主席树维护权值线段树了,只要维护一个普通线段树就好了。

算法设计:

  • 用原数组建立一个普通线段树
  • 每次要对数组的某一版本的某一位置进行修改,就相当于以主席树的相应版本的相应位置为模板新建执行一次单点修改的新版本;访问同理

这道题为什么需要先进行建树build操作呢?那道题不是不需要建树build操作嘛?

在那道题中,我们并不需要建树build操作,因为我们是使用主席树来维护权值线段树,权值线段树因为初始状态时树中都是0所以可以省略建树操作。但是这道题中,我们使用主席树来维护普通线段树, 但普通线段树的初始版本是有数值的,所以要先把原数组建立成一颗普通线段树,此时的这棵普通线段树就是版本0,之后我们的操作就会在版本0的基础上得到版本2。

一次询问也算作一次操作,那么怎么新建一个与之前版本一样的版本呢?要整体复制一遍吗?

题目要求如果询问的话,询问完了之后,还需要把这次的版本再拷贝一份,得到新版本。我们只需要让root[当前版本编号]=root[历史版本编号]就可以了,这样访问当前版本就会访问到历史版本上去,就相当于复制了一遍。

主席树每个版本的线段树维护什么信息?是区间和 还是 区间最大值什么的?

对于这个模板题来说,线段树里什么都不用维护,只需要让所有叶子结点里保存数值即可。


代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+10;
struct Node{
    //左右子树的编号  当前该节点的值
    int l,r,val;
}tr[N*40];
int a[N];   //原数组
//idx是给树中每个节点分配编号
//root[i]记录的是第i个版本的树根
int idx,root[N];
int n,m;

//先把原数组建立成普通线段树  当作版本0
//递归将区间[l,r]建立成线段树 u表示当前节点
//注意这里的u需要使用引用 建立完成之后 u还会把值传回去给root[0]
//表示第0个版本的树根
void build(int l,int r,int &u)
{
    u=++idx;    //给当前节点u分配一个编号
    //递归边界
    if(l==r)
    {
        //节点u的值存的就是val
        tr[u].val=a[l];
        return; //回溯
    }
    int mid=(l+r)>>1;   //分界点
    build(l,mid,tr[u].l);   //递归建立左子树
    build(mid+1,r,tr[u].r); //递归建立右子树
}


//修改操作:在ver版本的基础上 将pos位置上的值修改为val
void modify(int l,int r,int ver,int &u,int pos,int val)
{
    //给新版本中的当前节点u分配一个编号
    u=++idx;  
    //u是新版本,ver的旧版本,u要继承ver拥有的东西
    //将上一个版本ver的线段树拷贝给当前版本u的线段树
    tr[u]=tr[ver];
    //递归边界
    if(l==r)    //如果到了叶子节点
    {
        //将节点u的值修改为val
        tr[u].val=val;
        return; //回溯
    }
    int mid=(l+r)>>1;   //分界点
    if(pos<=mid)    //递归修改左子树
        modify(l,mid,tr[ver].l,tr[u].l,pos,val);
    else            //递归修改右子树
        modify(mid+1,r,tr[ver].r,tr[u].r,pos,val);
}

//访问版本u中pos位置上的值
int query(int l,int r,int u,int pos)
{
    //递归边界 找到了就返回
    if(l==r)
        return tr[u].val;
    int mid=(l+r)>>1; //分界点
    if(pos<=mid)    //递归左子树
        return query(l,mid,tr[u].l,pos);
    else            //递归右子树
        return query(mid+1,r,tr[u].r,pos);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    build(1,n,root[0]); //将把原数组建立成版本0的线段树
    int ver,op,x,y;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&ver,&op);
        if(op==1)
        {
            scanf("%d%d",&x,&y);
            //root[ver]是上一个版本  root[i]是当前版本
            modify(1,n,root[ver],root[i],x,y);
        }
        else if(op==2)
        {
            scanf("%d",&x);
            printf("%d\n",query(1,n,root[ver],x));
            //访问完了之后 题目要求生成新的版本
            root[i]=root[ver];
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值