可持久化数组

可持久化数组
P3919 【模板】
你需要维护这样的一个长度为 N 的数组,支持如下几种操作
在某个历史版本上修改某一个位置上的值
访问某个历史版本上的某一位置的值
每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
1.对于操作1,格式为 vi 1 loci valuei,即为在版本 vi 的基础上,将 aloci 修改为 valuei。
2.对于操作2,格式为 vi 2 loci,即访问版本 vi 中的 aloci 的值。
输入
5 10
59 46 14 87 41
0 2 1
0 1 1 14
0 1 1 57
0 1 1 88
4 2 4
0 2 5
0 2 4
4 2 1
2 2 2
1 1 5 91

输出
59
87
41
87
88
46
可持久化线段树最大的特点是:
可以访问历史版本。 简而言之,可持久化线段树,是在线段树上不断更新,但却不删除原有信息的线段树。每次更新都赋予一个新的根节点编号,用以区分不同的版本。由于可持续化线段树的结点的序号不确定。因此需要采取动态开点的方法构建线段树

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct node{
  int lc,rc;
  long long int v;
}segtree[maxn << 8];//可持久化线段树
int root[maxn << 5];//root[i]表示版本号为i的线段树的根节点编号
long long int a[maxn];//长度为 N 的数组
int n,m,tot;//n个点,m种操作,tot个点
int build_tree(int l, int r) {//传入区间,返回结点编号
  int pos = ++tot;//编号增加
  if (l == r) {//叶子结点
    segtree[pos].v = a[l];//当前编号结点的值就是数组中的数
    return pos;//返回结点编号
  }
  int mid = l + (r - l)/2;//区间二分
  segtree[pos].lc = build_tree(l, mid);//回溯当前结点左儿子编号,注意是MID与MID+1即非叶子结点不存值
  segtree[pos].rc = build_tree(mid + 1, r);//回溯当前结点右儿子编号
  return pos;//返回当前结点编号
}
//即访问版本 pos 中的 a[p] 的值
long long int query(int pos, int p, int l, int r) {//第POS个版本(传入根,实即往子树找)第P位,当前搜到的区间
  if (l == r)return segtree[pos].v;//叶子结点就返回结点值
  int mid = l + (r - l)/2;//得区间中点
  if(p <= mid) return query(segtree[pos].lc, p, l, mid);//往左儿子搜,左儿编号递归,区间减半
  else return query(segtree[pos].rc, p, mid + 1, r);//往右儿子搜,左儿编号递归,区间减半
}
//在版本 old 的基础上,将 a[tar] 修改为 c
int update(int old, int tar, int c, int l, int r) {//第OID个版本(传入根,实即往子树找)第TAR位改为C
  int pos = ++tot;//新开节点时,需要依靠前面构建的节点编号+1
  if (l == r) {//叶子结点赋值返回结点编号
    segtree[pos].v = c;
    return pos;
  }
  segtree[pos].lc = segtree[old].lc;//当前左子就是旧版左儿,注意如果更新结点在左子树下面可能就会被修改
  segtree[pos].rc = segtree[old].rc;//当前右儿就是旧版右儿,注意如果更新结点在右子树下面可能就会被修改
  int mid = l + (r - l)/2;//得区间折半
  if(tar <= mid) segtree[pos].lc = update(segtree[old].lc, tar, c, l, mid);//往左区间搜
  else segtree[pos].rc = update(segtree[old].rc, tar, c, mid + 1, r);//往右区间搜(注意只往一边搜)
  return pos;//返回结点编号
}
int main(){
    while (scanf("%d %d", &n, &m) != EOF) {//N个点,M个操作
    tot = 0;//初始化0个点
    for (int i = 1;i <= n; i++)scanf("%lld", &a[i]);//输入N个点
    root[0] = build_tree(1,n);//对一到N区间建树
    int v,x,l,w;
    for (int i = 1;i <= m; i++){
      scanf("%d %d %d", &v, &x, &l);
      if (x == 1) {
        scanf("%d", &w);
        root[i] = update(root[v], l, w, 1, n);
      } else {
        root[i] = root[v];
        printf("%lld\n",query(root[v], l, 1, n));
      }
    }
  }
  return 0;
}
算法:每个结点三个值,编号,左儿编号,右儿编号,叶子结点还要管数组值
每个结点都控制一个区间,左右儿子区间都是父结点区间一半,区间不在结构体中存,而作为函数的参数进行控制

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值