[模板]可持久化数组
Posted -wallace-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[模板]可持久化数组相关的知识,希望对你有一定的参考价值。
干货!
概述
可持久化数组
是借助可持久化线段树实现的,因为这个数据结构进行版本复制时只需新开 (log n) 个结点,单点查询也只需要花费 (log n) 的时间,非常高效。其主要思想就是先建一个线段树,动态开点,在叶子结点上维护原数组的信息。当修改某个位置的值时,只需要在这个位置所对应的叶结点的路径复制一遍即可。因为线段树有 (log n) 层,所以一次路径复制最多复制 (log n) 个结点。
实现
指针版实现方法。
用了一个 struct
给它封装了一下,使用更加方便。
先放代码:
template<typename Tp,int N>
struct PerArray
{
struct Node
{
Node *l,*r;
Tp val;
Node(Node *_l=NULL,Node *_r=NULL,Tp _v=Tp()):l(_l),r(_r),val(_v){}
};
Node* root[N+5];
int cur;
#define mid ((l+r)>>1)
Tp __buf[N+5];
Node* __build(int l,int r)
{
Node *rt=new Node();
if(l==r) return rt->val=__buf[l],rt;
rt->l=__build(l,mid);
rt->r=__build(mid+1,r);
return rt;
}
Node* __upd_val(Node *pre,int l,int r,int p,Tp v)
{
Node *rt=new Node(pre->l,pre->r);
if(l==r) return rt->val=v,rt;
if(p<=mid) rt->l=__upd_val(pre->l,l,mid,p,v);
else rt->r=__upd_val(pre->r,mid+1,r,p,v);
return rt;
}
Tp __get_val(Node *rt,int l,int r,int p)
{
if(l==r) return rt->val;
if(p<=mid) return __get_val(rt->l,l,mid,p);
else return __get_val(rt->r,mid+1,r,p);
}
#undef mid
PerArray()
{
for(register int i=1;i<=N;i++)
root[i]=NULL;
}
void init(Tp *s,Tp *t)
{
for(Tp *p=s;p!=t;p++) __buf[p-s+1]=*p;
root[cur=0]=__build(1,N);
}
void copy(int ver)
{
root[++cur]=root[ver];
}
void update(int b,int p,Tp v)
{
root[++cur]=__upd_val(root[b],1,N,p,v);
}
Tp at(int p)
{
return __get_val(root[cur],1,N,p);
}
};
虽然这玩意又臭又长, 但还是非常方便的。
功能示例
但具体怎么用呢?见下:
- Cautious:不要随便使用以双下划线开头的变量或函数;不要随意修改 root,cur。
//功能示例:
PerArray<Tp,N> seq;
//声明一个名为 seq ,类型为 Tp ,长度为 N 的可持久化数组。
//其中下标的范围为 [1,N],即从 1 开始。
//刚刚声明时,还不能立即使用
seq.init(a+1,a+1+n);
//初始化,将数组a[1,...,n]导入到可持久化数组 seq 中
seq.at(i)
//获取 seq 当前版本的第 i个位置的值
seq.copy(i)
//创建一个版本,并将第 i 版本的复制到该版本上
//上面这句话与“创建一个以版本 i 为基础的版本”同义
seq.update(i,j,k)
//创建一个以版本 i 为基础的版本,并同时在该版本上第 j 个位置的值修改为 k。
实践
P3919 【模板】可持久化数组(可持久化线段树/平衡树)
这是可持久化数组的模板题。
#include<cstdio>
using namespace std;
/*
将快读模板 & 上面的可持久化数组模板粘贴在此处
*/
int n,m,a[1000005];
PerArray<int,1000000> ary;
signed main()
{
using namespace fastIO_int;
read(n,m);
for(register int i=1;i<=n;i++)
a[i]=get_int();
ary.init(a+1,a+1+n);//导入数组
for(register int i=1;i<=m;i++)
{
int ver,cmd,loc,value;
read(ver,cmd,loc);
if(cmd==1)
{
read(value);
ary.update(ver,loc,value);//更新操作
}
else
{
ary.copy(ver);//先复制版本
write(ary.at(loc));//取值操作
putchar('
');
}
}
return 0;
}
然而不幸的是常数太大最后一个点死活过不了
P3402 可持久化并查集
是时候试着实现一下可持久化数组的衍生功能——可持久化并查集了。
使用按秩合并(按深度合并),不用路径压缩,防止更新操作过多而 MLE。
这里的实现已经和普通按秩合并的并查集很像了,这就是封装的好处了。
#include<cstdio>
using namespace std;
/*
将快读模板 & 上面的可持久化数组模板粘贴在此处
*/
signed main()
{
using namespace fastIO_int;
read(n,m);
for(register int i=1;i<=n;i++) a[i]=i;
fa.init(a+1,a+1+n);
for(register int i=1;i<=n;i++) a[i]=1;
dep.init(a+1,a+1+n);
for(register int i=1;i<=m;i++)
{
int cmd,x,y,k;
read(cmd);
switch(cmd)
{
case 1:
read(x,y);
x=find(x),y=find(y);
if(x!=y)
{
if(dep.at(x)==dep.at(y))
fa.update(i-1,x,y),dep.update(i-1,y,dep.at(y)+1);
else if(dep.at(x)<dep.at(y))
fa.update(i-1,x,y),dep.copy(i-1);
else
fa.update(i-1,y,x),dep.copy(i-1);
}
else
fa.copy(i-1),dep.copy(i-1);
break;
case 2:
read(k);
fa.copy(k),dep.copy(k);
break;
case 3:
read(x,y);
fa.copy(i-1),dep.copy(i-1);
x=find(x),y=find(y);
if(x==y) puts("1");
else puts("0");
}
}
return 0;
}
具体跑得比不封装的写法稍慢。不过也不影响。
附
快读模板:
namespace fastIO_int{
int get_int()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
x=x*10+c-'0',c=getchar();
return f*x;
}
void read(){}
template<class T1,class ...T2>
void read(T1 &i,T2&... rest)
{
i=get_int();
read(rest...);
}
void put_int(int x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)put_int(x/10);
putchar(x%10+'0');
}
void write(){}
template<class T1,class ...T2>
void write(T1 i,T2... rest)
{
put_int(i),putchar(' ');
write(rest...);
}
};
如果想要添加一些功能,可以自己改改。
若有不妥之处请在评论区指正,感谢阅读!
以上是关于[模板]可持久化数组的主要内容,如果未能解决你的问题,请参考以下文章
luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)