[模板]可持久化数组

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...);
    }
};

如果想要添加一些功能,可以自己改改。

若有不妥之处请在评论区指正,感谢阅读!

以上是关于[模板]可持久化数组的主要内容,如果未能解决你的问题,请参考以下文章

P3919 模板可持久化数组(可持久化线段树/平衡树)

[模板]可持久化数组

[模板] 可持久化数组

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

[解题报告]P3919 模板可持久化数组(可持久化线段树/平衡树)

[模板] 数据结构