可持久化学习总结

大体思路

可持久化,就是按照时间(例如T个时间点),每一个时间点建立一个数据结构。暴力做肯定会炸,所以通过指向历史节点的方法来节省空间,时间,从而实现访问历史情形。
至于具体是如何指向历史节点的,请直接看下面的例子

1.可持久化线段树(又名主席树/函数式线段树)

(这个详讲,后面的数据结构都差不多同理,毕竟我懒

举个版题:POJ 2104

题意就是给出一个数组(10w),和一些询问(5k),每次询问给出一个区间[a,b](数组下标)和k,表示求[a,b]中第k大的数。

这道题有很多种解法,这里只讨论可持久化线段树做法

我们可以先读入数据,离散化,按照数组下标划分时间版本(即输入数组第一个数后版本为1,输入第二个数后版本为2),然后将线段树维护的值sum定义为:在这个版本上的所有数,在[l,r]区间内的有多少个。这样的话询问数组下标[a,b]中第k大时,就可以将版本b和版本a-1的同一区间的维护值相减,来得到[a,b]的实际sum,然后在树里按往常的方法搜,即,如果当前节点左儿子(两个版本相减)的sum大于等于当前k,就往左找,否则往右找,到叶子时,这个节点的[l,r],l或者r(l=r)就是要找的数。

接下来就是可持久化的关键:指向上一版本节点

当我们读入第i个数时(记为a),加入,那么线段树中受影响的节点数只有log(n)个,它跟上一个版本不一样节点的也只有log(n)个那么仅新建受影响的节点,sum=上一个版本sum+1,就可以了,这些节点的左右儿子,除了指向新建的节点的,都指向上一个版本的节点

PS:建议将未加入任何数的版本build出来作为0版本

举个例子:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

形象的说,只有从上往下的一条线上的节点改了,数量就是线段树深度log(n)

附上爆丑无比的 AC代码:

#include<cstdio>
#include<algorithm>
#define MAXN 100006
using namespace std;
int b[MAXN],aa,bb,a[MAXN],root[MAXN];
int n,m,cnt,bn,k;
struct node
{
    int ls,rs;
    int sum;
}I[MAXN*20];
int place(long long x)
{
    int l=1,r=bn;
    while(l<r)
    {
        int mid=(l+r)/2;
        if(b[mid]>x) r=mid-1;
        if(b[mid]==x) break;
        if(b[mid]<x) l=mid+1;
    }
    return (l+r)/2;
}
void build(int now,int l,int r)
{
    if(l==r) return;
    int mid=(l+r)/2;
    I[now].ls=++cnt;
    build(I[now].ls,l,mid);
    I[now].rs=++cnt;
    build(I[now].rs,mid+1,r);
}
void insert(int now,int last,int x,int l,int r)
{
    int mid=(l+r)/2;
    if(l==r) return;
    if(x>mid)
    {
        I[now].ls=I[last].ls;
        I[now].rs=++cnt;
        I[cnt].sum=I[I[last].rs].sum+1;
        insert(cnt,I[last].rs,x,mid+1,r);
    }
    else
    {
        I[now].rs=I[last].rs;
        I[now].ls=++cnt;
        I[cnt].sum=I[I[last].ls].sum+1;
        insert(cnt,I[last].ls,x,l,mid);
    }
}
int find(int now,int last,int k,int l,int r)
{
    int mid=(l+r)/2;
    if(l==r) return l;
    if(I[I[now].ls].sum-I[I[last].ls].sum<k) return find(I[now].rs,I[last].rs,k-I[I[now].ls].sum+I[I[last].ls].sum,mid+1,r);
    else return find(I[now].ls,I[last].ls,k,l,mid);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    bn=unique(b+1,b+1+n)-b-1;
    root[0]=++cnt;
    build(root[0],1,bn);
    for(int i=1;i<=n;i++)
    {
        root[i]=++cnt;
        I[root[i]].sum=I[root[i-1]].sum+1;
        insert(root[i],root[i-1],place(a[i]),1,bn);
    }
    for(int t=1;t<=m;t++)
    {
        scanf("%d%d%d",&aa,&bb,&k);
        printf("%d\n",b[find(root[bb],root[aa-1],k,1,bn)]);
    }
}

2.可持久化数组

建立线段树,除了叶子结点以外的其它所有节点,都只起记录左右儿子的作用,叶子结点记录数组下标对应的值,同样用之前的可持久化线段树的方式,指向历史节点就可以了。

3.可持久化并查集

并查集本体就是一个fa数组,只要通过可持久化线段树实现可持久化数组,再把并查集操作稍微改一下就可以了。

4.可持久化Tri(字典树)

同上

5.可持久化Treap

先说Treap:由于随机建树期望深度是log1.39(n)的,所以直接随机建树,通过给每一个数赋随机权值,加数时除了保证平衡树性质,同时保证子树任意值权值大于(或小于)当前节点权值即可。合并操作时,也要如此做。

然后可持久化就可以了(滑稽)
PS:这几个版我写的都奇丑,就懒得粘上来了,喵

猜你喜欢

转载自blog.csdn.net/Nuclear_fusion/article/details/78704015
今日推荐