题目大意:
你需要维护这样的一个长度为 N 的数组,支持如下几种操作
-
在某个历史版本上修改某一个位置上的值
- 访问某个历史版本上的某一位置的值
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
解题思路:很明显,本题需要用到可持久化数据结构。主席树即可。
主要思路就是,维护线段树,对每个节点进行标号。
这样做的好处是:由于之前版本的树是不会改变的,而本题只有单点查询,因此最多只会有$log_2 n$个节点的信息进行改变。进行标号以后,如果某棵子树没有改变,则直接将原来的编号保存下来即可,省时间又省空间。
对于题目的第一种操作,只要保存历史版本根节点的编号即可(整棵树没变化),然后单点查询。
对于题目的第二种操作,相当于单点修改,每次记录该节点的原编号和现编号,然后对于没有变化的一个子树就可以直接保存,有编化的修改即可。
说一说此题坑我的地方:本题对于不同数据范围,空间分配是不一样的(最低128MB,最高512MB),而我直接开了数组,导致被卡空间MLE。使用指针分配内存的方式成功解决此问题(代码第55~59行)。
C++ Code:
#include<cstdio> #include<cstring> #include<cctype> #define N 1000005 int n,m,rt[N],cnt,Q,p; int *ld,*rd,*d; inline int readint(){ char c=getchar(); bool b=false; for(;!isdigit(c);c=getchar())b=c=='-'; int d=0; for(;isdigit(c);c=getchar())d=(d<<3)+(d<<1)+(c^'0'); return b?-d:d; } inline void putint(int d){ if(d==0)putchar('0');else{ if(d<0)putchar('-'),d=-d; int w=1; for(;w<=d;w*=10); for(w/=10;w;w/=10) putchar((d/w%10)^'0'); } putchar(' '); } void build_tree(int l,int r,int o){ if(l==r){ d[o]=readint(); return; } int mid=(l+r)>>1; build_tree(l,mid,ld[o]=++cnt); build_tree(mid+1,r,rd[o]=++cnt); } void query(int l,int r,int o){ if(l==r)putint(d[o]);else{ int mid=(l+r)>>1; if(Q<=mid)query(l,mid,ld[o]);else query(mid+1,r,rd[o]); } } void change(int l,int r,int old,int now){ if(l==r)d[now]=p;else{ int mid=(l+r)>>1; if(Q<=mid){ rd[now]=rd[old]; change(l,mid,ld[old],ld[now]=++cnt); }else{ ld[now]=ld[old]; change(mid+1,r,rd[old],rd[now]=++cnt); } } } int main(){ n=readint(),m=readint(); if(n<=150000){ d=new int[N<<2];ld=new int[N<<2];rd=new int[N<<2]; }else{ d=new int[N*20];ld=new int[N*20];rd=new int[N*20]; } memset(rt,0,sizeof rt); memset(ld,0,sizeof ld); memset(rd,0,sizeof rd); build_tree(1,n,rt[0]=cnt=1); for(int i=1;i<=m;++i){ int v=readint(),opt=readint(); Q=readint(); if(opt==2)query(1,n,rt[i]=rt[v]);else{ rt[i]=++cnt; p=readint(); change(1,n,rt[v],rt[i]); } } return 0; }