可持久化数组入门

摘要:
#位置主题链接:[持久数组](https://www.luogu.org/problemnew/show/P3919)主题描述如下。您需要维护这样一个长度为NN的数组。支持以下操作:修改历史版本中某个位置的值,以访问历史版本中特定位置的值。此外,每次执行操作时都会生成一个新版本(对于操作2,它是在不进行任何更改的情况下生成一个相同的版本)。版本号是当前操作的编号(从1开始,
# 洛谷题目链接:[可持久化数组](https://www.luogu.org/problemnew/show/P3919)

题目描述

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

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

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

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

输入输出格式

输入格式:

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

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

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

  1. 对于操作1,格式为(v_i 1 {loc}_i {value}_i) ,即为在版本(v_i)​ 的基础上,将 (a_{{loc}_i})​ 修改为 ({value}_i)

  2. 对于操作2,格式为(v_i 2 {loc})​ ,即访问版本(v_i) 中的 (a_{{loc}_i}) 的值

输出格式:

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

输入输出样例

输入样例#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,1≤N,M≤10^3)

对于50%的数据:(1 leq N, M leq {10}^4,1≤N,M≤10^4)

对于70%的数据:(1 leq N, M leq 10^5,1≤N,M≤10^5)

对于100%的数据:(1 leq N, M leq {10}^6, 1 leq {loc}_i leq N, 0 leq v_i < i, {-10}^9 leq a_i, {value}_i leq {10}^9)

经测试,正常常数的可持久化数组可以通过,请各位放心

数据略微凶残,请注意常数不要过大

另,此题I/O量较大,如果实在TLE请注意I/O优化

询问生成的版本是指你访问的那个版本的复制

样例说明:

一共11个版本,编号从0-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

概述一下题意:给出一个序列,支持单点修改,以及单点查询历史版本.
如果直接用数组统计,很显然是很不好保存修改的历史版本的.

于是我们可以考虑如何记录历史版本.很显然,最暴力的办法就是直接开数组存下每个历史版本的所有值,然后在这些数组中查询.但是这样的空间复杂度是O((N^2))的.对于大数据是存不下来的.

既然存不下来,那么我们就考虑一下如何优化空间的使用.
如果直接用数组一个下标一个下标的存,很显然是没有空间可以挤出来的.这里我们考虑用线段树的方式存储节点,将原数组将入到一颗线段树中.

看到这里可能有人会说:线段树的空间复杂度不是比数组模拟要高吗?

但是,我们在修改历史版本的时候,每修改一次,事实上就是在上一个版本中修改了一个值,那么我们可以通过共用节点的方式,将没有被修改的节点共用,这样就可以极大限度的省出空间.这样可以被证明空间复杂读是O((NlogN))的.由于线段树形的结构,时间复杂度同样也是O((NlogN))的.

然后在找一个修改版本的时候直接通过该版本的线段树的根节点来找.

下面看一下代码的注释吧.

#include<bits/stdc++.h>
using namespace std;
const int N=1000000+5;

int n, m, cnt = 0;//cnt记录节点编号
int w[N], root[N];//w记录权值,root[i]记录第i个修改版本

struct persistent_tree{
    int ls, rs, val;//记录左右儿子,权值只有叶子节点才需要记录
}t[N*21];

int gi(){
    int ans = 0 , f = 1; char i = getchar();
    while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
    while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
    return ans * f;
}

void build(int &node,int l,int r){//建树,在到新节点的时候要记录,所以这里要传地址
    node = ++cnt;
    if(l == r){t[node].val = w[l]; return;}
    int mid = (l+r>>1);
    build(t[node].ls,l,mid);
    build(t[node].rs,mid+1,r);
}

void updata(int &node,int last,int l,int r,int pos,int val){
    node = ++cnt; t[node] = t[last];//复制上一个版本
    if(l == r){t[node].val = val; return;}//权值只需要记录在叶子节点,所以只需要在找到叶子节点的时候修改
    int mid = (l+r>>1);
    if(pos <= mid) updata(t[node].ls,t[last].ls,l,mid,pos,val);//左右递归找到修改的位置
    else updata(t[node].rs,t[last].rs,mid+1,r,pos,val);
}

int query(int node,int l,int r,int pos){//线段树基本操作
    if(l == r) return t[node].val;
    int mid = (l+r>>1);
    if(pos <= mid) return query(t[node].ls,l,mid,pos);
    else return query(t[node].rs,mid+1,r,pos);
}

int main(){
    //freopen("data.in","r",stdin);
    int last, flag, pos, val; n = gi(); m = gi();
    for(int i=1;i<=n;i++) w[i] = gi();
    build(root[0],1,n);
    for(int i=1;i<=m;i++){
	last = gi(); flag = gi();
	if(flag == 1){
	    pos = gi(); val = gi();
	    updata(root[i],root[last],1,n,pos,val);
	}
	else printf("%d
",query(root[last],1,n,gi())) , root[i] = root[last];//第i次是修改操作直接保存上一次修改的操作
    }
    return 0;
}

免责声明:文章转载自《可持久化数组入门》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇TortoiseGit 使用教程DEV控件之ChartControl用法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

LoadRunner参数数组

参数数组提供了对一类参数集中存放的机制,其中LR内置的几个函数有:lr_paramarr_idx()、lr_paramarr_len()、lr_paramarr_random() 同时参数数组必须满足一下两个条件:①参数必须都是以相同的名字开头的,后接下划线加数字的方式顺序赋值;②参数数组必须要有一个“参数名_count”的参数来记录数字的长度。 eg:...

Python 持久化管理之 Pickle/ZODB

1.对象持久化 如果希望透明地存储 Python 对象,而不丢失其身份和类型等信息,则需要某种形式的对象序列化: 它是一个将任意复杂的对象转成对象的文本或二进制表示的过程。同样,必须能够将对象经过序列化后的形式恢复到原有的对象。 在 Python 中,这种序列化过程称为 pickle,可以将对象 pickle 成字符串、磁盘上的文件或者任何类似于文件的对象...

JS中的map()方法

map定义和方法 map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理的后值。 map()方法按照原始数组元素顺序依次处理元素。 注意: map不会对空数组进行检测 map不会改变原始数组 arr.map(function(currentValue,index,arr),thisValue) 参数说明 function(currentVal...

php变量类型及几个常用的打印方式

变量的数据类型: 1,标量类型:int (整型),float(浮点型),boolean(布尔型),string(字符串型) 2,复合类型:array(数组),object(对象) 3,特殊类型:null(空),resource(资源) 几个常用的打印方式: 1,echo输出一个或多个字符串,他是PHP语句,不是函数,所以他没有返回值 <?php...

js forEach参数详解,forEach与for循环区别,forEach中如何删除数组元素

 壹 ❀ 引 在JS开发工作中,遍历数组的操作可谓十分常见了,那么像for循环,forEach此类方法自然也不会陌生,我个人也觉得forEach不值得写一篇博客记录,直到我遇到了一个有趣的问题,我们来看一段代码: let arr = [1, 2]; arr.forEach((item, index) => { arr.splice(inde...

JS案例之8——从一个数组中随机取数

近期项目中遇到一个需求,从一个列表中随机展示列表的部分内容,需求不大,JS也非常容易实现。主要是运用到了Math对象的random方法,和Array的splice方法。 思路是先新建一个数组,存放所有的列表,然后算出随机数,从数组中取出这个随机索引对应的值,然后组成一个随机数组。 源代码如下: 1 <!DOCTYPE html> 2 <...