例题
特点:在普通线段树支持查询当前状态基础上,支持查询过去的所有版本
一、( ext{基础思路}):
暴力保存过去版本,每一次修改都会造成(nlogn)的时空复杂度,总复杂度(O)((mnlogn)),直接上天
注意到每一次单点修改只会导致对应的叶子节点以及它的所有祖先存储的值改变,我们可以考虑只存储这一条链,将这条链连到原来的树上,单次时空复杂度(logn)
每次修改一定会影响根节点,所以每次修改都会换一个新的根,并且可以看出,由每个根向下都可以遍历出一颗形状完全相同的线段树,所以每个根直接对应自己的版本,访问一个版本直接从这个根向下就行了
注:可持久化线段树要存左右儿子,不能直接2或者2+1表示左右儿子
二、( ext{流程})
线段树其他所有操作不变(除了根节点变了),只影响查询操作
查询向下递归的过程中,每到一个点,申请一个新节点(t),其信息和当前点完全相同,然后继续向下,如果向左递归就将左儿子修改为左边即将有的新节点编号,右儿子不变,如果右递归则相反。回溯时同样也需要(pushup)
(luogu) 3919
(Code)
这道题只有单点查询,不需要维护中间节点,只需要维护叶子节点的值就行了,实际上进一步削弱了难度(甚至不需要这个算法(雾))
#include<bits/stdc++.h>
#define N 6000005
using namespace std;
int n,m;
int a[N];
int root[N],sum[N*4],ls[N*4],rs[N*4],ndsum;
template <class T>
void read(T &x)
{
char c;int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void build(int &rt,int l,int r)//初始建树
{
if(!rt) rt=++ndsum;
if(l==r)
{
sum[rt]=a[l];
return;
}
int mid=(l+r)>>1;
build(ls[rt],l,mid);
build(rs[rt],mid+1,r);
}
void copynode(int x,int y)//复制
{
ls[x]=ls[y];
rs[x]=rs[y];
sum[x]=sum[y];
}
int modify(int rt,int l,int r,int x,int v)//一次修改
{
int t=++ndsum;
copynode(t,rt);//复制这个节点
if(l==x&&r==x)
{
sum[t]=v;
return t;
}
int mid=(l+r)>>1;
if(mid>=x) ls[t]=modify(ls[rt],l,mid,x,v);//左边
else rs[t]=modify(rs[rt],mid+1,r,x,v);//右边
return t;//返回这个点的编号
}
int query(int rt,int l,int r,int x)
{
if(l==x&&r==x) return sum[rt];
int mid=(l+r)>>1;
if(mid>=x) return query(ls[rt],l,mid,x);
else return query(rs[rt],mid+1,r,x);
}
int main()
{
read(n);read(m);
for(int i=1;i<=n;++i) read(a[i]);
build(root[0],1,n);
for(int i=1;i<=m;++i)
{
int v,k,x,y;
read(v),read(k);read(x);
if(k==1)
{
read(y);
root[i]=modify(root[v],1,n,x,y);//从root[v]开始的
}
else
{
root[i]=root[v];//询问也算一次操作,没有修改直接就copy一遍v就行
printf("%d
",query(root[v],1,n,x));
}
}
return 0;
}