题目大意:
你需要维护这样的一个长度为 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('\n');
}
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;
}