【数据结构】可持久化权值线段树(又名主席树)
1. 废话
我从小就有个梦想——学习主席树
2. idea
例题: 【模板】可持久化线段树 1 (可持久化数组)
给出一个序列,现在有两种操作:单点修改和单点查询,并且每次操作都会生成一个新版本,我们在进行操作的时候也会给出基于的版本,例如给出序列:2 3 5 4(这是0版),现在查询第2个数(生成1版),再将第1版第3个数改成2,将第2版的第1个数改成5,查询第3版第3个数,将第2版第4个数改成1,最后查询第5版第2个数
用暴力的,每个版本都重新生成数组的方法表达就是这样:
0版 2 3 5 4
1版 2 3 5 4 输出3
2版 2 3 2 4
3版 5 3 2 4
4版 5 3 2 4 输出2
5版 2 3 1 4
6版 2 3 1 4 输出3
显然,这样空间太离谱了,所以我们考虑优化
可以发现,我们每次都是单点修改,所以我们可以只记录修改信息,于是将它拓展为树形结构
3张图涵盖建树、查询、修改
3. 建树
按正常线段树建树方式建树即可
4. 查询
加一个根节点,使之连在复制版本的左右子树上,这样查询时效果与复制没有区别
5. 修改
如果修改下标在左子树的维护范围内,则连右子树,新生成左子树节点。右子树类似,到叶子节点时则直接存修改的数字即可
6. Detail
可以发现,每个版本都会生成一个新的根,则建数组保存各个版本的根,涉及版本时传入对应根节点即可。另外可以发现,每一个根节点都可以分离出来一个完整的线段树
7. 时空复杂度
7.1 空间复杂度
每次加点会多为
7.2 时间复杂度
因为分离的性质,所以为
8. 代码
都讲到这里了,我认为已经可以对着图打出来了,但还是放一下吧……
#include <iostream>
#include <cstdio>
using namespace std;
struct node
{
int dat,ls,rs;
}tr[40000005];
int n,m,a[2000005],ttop,task,x,y,z,root[2000005],version;
int makenode(int dat=0)
{
ttop++;
tr[ttop].dat=dat;
return ttop;
}
int build(int l,int r)
{
int now=makenode();
if(l==r)
{
tr[now].dat=a[l];
return now;
}
int mid=(l+r)>>1;
tr[now].ls=build(l,mid);
tr[now].rs=build(mid+1,r);
return now;
}
int change(node copy,int l,int r,int p,int k)
{
int now=makenode();
if(l==r)
{
tr[now].dat=k;
return now;
}
int mid=(l+r)>>1;
if(p<=mid)
{
tr[now].ls=change(tr[copy.ls],l,mid,p,k);
tr[now].rs=copy.rs;
}
if(mid+1<=p)
{
tr[now].ls=copy.ls;
tr[now].rs=change(tr[copy.rs],mid+1,r,p,k);
}
return now;
}
int query(int now,int l,int r,int p)
{
if(l==r) return tr[now].dat;
int mid=(l+r)>>1;
if(p<=mid) return query(tr[now].ls,l,mid,p);
else return query(tr[now].rs,mid+1,r,p);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
root[0]=build(1,n);
while(m--)
{
scanf("%d%d%d",&x,&task,&y);
if(task-1)
{
root[++version]=makenode();
tr[root[version]]=tr[root[x]];
cout<<query(root[x],1,n,y)<<'\n';
}
else
{
scanf("%d",&z);
root[++version]=change(tr[root[x]],1,n,y,z);
}
}
return 0;
}
注意常数优化QAQ