洛谷P3919 【模板】可持久化线段树 1(可持久化数组)

tags

线段树 主席树

中文题面

如题,你需要维护这样的一个长度为 N 的数组,支持如下几种操作

在某个历史版本上修改某一个位置上的值

访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

输入格式

输入的第一行包含两个正整数 N N N, M M M, 分别表示数组的长度和操作的个数。

第二行包含 N N N 个整数,依次为初始状态下数组各位的值(依次为 a i a_i ai 1 ≤ i ≤ N 1≤i≤N 1iN)。

接下来 M M M 行每行包含3或4个整数,代表两种操作之一( i i i 为基于的历史版本号):

对于操作1,格式为 v i   1   l o c i   v a l u e i v_i\ 1\ loc_i\ value_i vi 1 loci valuei​,即为在版本 v i v_i vi ​的基础上,将 a [ l o c i ] a[loc_i] a[loci]​​ 修改为 v a l u e i value_i valuei​。

对于操作2,格式为 v i   2   l o c i v_i\ 2\ loc_i vi 2 loci​,即访问版本 v i v_i vi ​中的 a [ l o c i ] a[loc_i] a[loci] ​​的值,注意:生成一样版本的对象应为 v i v_i vi​。

输出格式

输出包含若干行,依次为每个操作2的结果。
1 ≤ N , M ≤ 106 , 1 ≤ l o c i ≤ N , 0 ≤ v i < i , − 1 0 9 ≤ a i , v a l u e i ≤ 1 0 9 1≤N,M≤106,1≤loc_i≤N,0≤v_i<i,−10^9≤a_i,value_i≤10^9 1N,M106,1lociN,0vi<i,109ai,valuei109

思路

如果对于每次修改都复制一遍数组,空间复杂度为 O ( N M ) O(NM) O(NM),显然不合适。考虑到每次修改只变了数组的一小部分,我们考虑对这一小部分进行复制。把数组按照线段树的方式划分,得到以下结构:
在这里插入图片描述我们发现修改箭头所指的值时,只有黄线标记的节点受到了影响,这些点的数量为 l o g n log n logn,只需每次对这些节点进行复制,其他未修改的点保留指向即可。
注:主席树一般开40倍空间

代码

#include <bits/stdc++.h>
using namespace std;

int cnt = 0, a[1000005], root[1000005], n, m;
int ver, op, loc, value;
struct node {
    int l, r, val;
} tree[40000005];
int clone(int cp) {
    tree[++cnt] = tree[cp];
    return cnt;
}
int build(int now, int l, int r) {
    if (l == r) {
        tree[now].val = a[l];
        return now;
    }
    int mid = l + r >> 1;
    tree[now].l = build(++cnt, l, mid);
    tree[now].r = build(++cnt, mid + 1, r);
    return now;
}
int update(int now, int l, int r, int x, int v) {
    int newnew = clone(now);
    if (l == r) {
        tree[newnew].val = v;
        return newnew;
    }
    int mid = l + r >> 1;
    if (x > mid) tree[newnew].r = update(tree[newnew].r, mid + 1, r, x, v);
    else tree[newnew].l = update(tree[newnew].l, l, mid, x, v);
    return newnew;
}
int query(int now, int l, int r, int x) {
    if (l == r) return tree[now].val;
    int mid = l + r >> 1;
    if (x > mid) return query(tree[now].r, mid + 1, r, x);
    return query(tree[now].l, l, mid, x);
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    root[0] = build(++cnt, 1, n);
    for (int i = 1; i <= m; i++) {
        cin >> ver >> op >> loc;
        if (op == 1) {
            cin >> value;
            root[i] = update(root[ver], 1, n, loc, value);
        } else {
            cout << query(root[ver], 1, n, loc) << '\n';
            root[i] = root[ver];
        }
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值