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

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

题目背景

2021.9.18 增添一组 hack 数据 by @panyf。

标题即题意。

有了可持久化数组,便可以实现很多衍生的可持久化功能。(例如:可持久化并查集)

题目描述

如题,你需要维护这样的一个长度为 $ N $ 的数组 \(a\),支持如下两种操作:

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

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

此外,每进行一次操作,就会生成一个新的版本。版本编号即为当前操作的编号(从 \(1\) 开始编号,版本 \(0\) 表示初始状态数组)。

对于操作 \(2\),即为生成一个完全一样的版本,不作任何改动。即,询问生成的版本是询问所访问的那个版本的复制。

输入格式

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

第二行包含 $ N $ 个整数,其中第 \(i\) 个数为 \(a_i\)。

接下来 $ M $ 行每行包含 \(3\) 或 \(4\) 个整数,代表两种操作之一(设当前是第 \(i\) 次操作):

  1. 对于操作 \(1\),格式为 v 1 p c,即为在版本 $ v $ 的基础上,将 $ a_{p} $ 修改为 \(c\)。

  2. 对于操作 \(2\),格式为 v 2 p,即访问版本 $ v $ 中的 $ a_{p} $ 的值,注意:此时应生成与版本 \(v\) 完全相同的新版本。

输出格式

输出包含若干行,依次为每个操作 \(2\) 的结果。

输入输出样例 #1

输入 #1

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

输出 #1

59
87
41
87
88
46

说明/提示

数据规模

对于 \(30\%\) 的数据,$ 1 \leq N, M \leq {10}^3 $。

对于 \(50\%\) 的数据,$ 1 \leq N, M \leq {10}^4 $。

对于 \(70\%\) 的数据,$ 1 \leq N, M \leq {10}^5 $。

对于 \(100\%\) 的数据:

  • $ 1 \leq N, M \leq {10}^6$;
  • \(1 \leq p \leq N\);
  • 设当前是第 \(x\) 次操作,\(0 \leq v < x\);
  • \(-{10}^9 \leq a_i,c \leq {10}^9\)。

样例说明

所有操作结束后,总共生成了 \(11\) 个版本,编号为 \(0 \sim 10\),依次为:

版本 \(0\):59 46 14 87 41,

版本 \(1\):59 46 14 87 41,

版本 \(2\):14 46 14 87 41,

版本 \(3\):57 46 14 87 41,

版本 \(4\):88 46 14 87 41,

版本 \(5\):88 46 14 87 41,

版本 \(6\):59 46 14 87 41,

版本 \(7\):59 46 14 87 41,

版本 \(8\):88 46 14 87 41,

版本 \(9\):14 46 14 87 41,

版本 \(10\):59 46 14 87 91。

算法分析

首先,这是一题模板题,就是可持久化线段树,非常像线段树模板,但这是主席树。主席树就是记录历史时刻的树,修改要新建节点,记录每一次修改了哪些节点,然后剩下的就是普通的线段树了。

AC代码

#include<bits/stdc++.h>
using namespace std;
struct node{//一个节点的信息
    int l,r,val;//左儿子,右儿子,权值
};
int n,m;
int cnt = 0;
int a[1000005];
int r[32000005];
node t[32000005];
int make(int pos){//创新节点
    t[++cnt] = t[pos];//传递信息
    return cnt;
}
int build(int l,int r,int pos){//建树
    pos = ++cnt;
    if(l==r){
        t[pos].val = a[l];
        return cnt;
    }
    int mid = (l+r)>>1;
    t[pos].l = build(l,mid,t[pos].l);
    t[pos].r = build(mid+1,r,t[pos].r);
    return pos;
}
int update(int index,int val,int l,int r,int pos){//修改
    pos = make(pos);//修改要新建节点!!!
    if(l==r){
        t[pos].val = val;
        return pos;
    }
    int mid = (l+r)>>1;
    if(index<=mid){
        t[pos].l = update(index,val,l,mid,t[pos].l);
    }else{
        t[pos].r = update(index,val,mid+1,r,t[pos].r);
    }
    return pos;
}
int query(int index,int l,int r,int pos){//查询
    if(l==r){
        return t[pos].val;
    }
    int mid = (l+r)>>1;
    if(index<=mid){
        return query(index,l,mid,t[pos].l);
    }else{
        return query(index,mid+1,r,t[pos].r);
    }
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
    cin>>n>>m;
    for(int i = 1; i<=n; i++){
        cin>>a[i];
    }
    r[0] = build(1,n,0);//记录原来的节点
    for(int i = 1; i<=m; i++){
        int v,op,p,c;
        cin>>v>>op>>p;
        if(op==1){
            cin>>c;
            r[i] = update(p,c,1,n,r[v]);
        }else{
            cout<<query(p,1,n,r[v])<<'\n';
            r[i] = r[v];//更新节点
        }
    }
    return 0;
}

总结

本题就是一个模板题,只需要去学一下可持久化线段树就行了,确实可以评蓝题。

posted @ 2026-10-05 15:04  xmgene  阅读(3)  评论(2)    收藏  举报