浅谈可持久化数据结构

Preface

由于我真的是太弱了,所以真的是浅谈

神奇的数据结构其实我也很虚啊!


值域线段树

简单的说,值域线段树区间里面存的是在这个区间内的数的个数有多少个。

有没有感觉很简单,考虑一下如果我们有一棵这样的线段树,查找排名为rk的数时只需要看一下左子树的大小就可以判断在左边还是右边了。

有没有感觉很像BST


动态开点与可持久化

根据上面的值域线段树,我们可以得出一种naive的做法:

对于每一个前缀\([1,i](i\in[1,n])\)都开一棵值域线段树,然后查询区间的时候直接每个节点的size都变成\(size(r)-size(l-1)\),就是把前缀和的思想搬到了树上

然后不仅要MLE,每次复制线段树都T了

我们考虑优化一波,我们发现每一次操作只会改变从根节点到叶子节点的一条路径上点的值。

然后由于线段树的深度是稳定且均匀的\(logn\),因此我们发现许多节点的信息都是可以共用的:

我们在修改红色的这一条路径的信息时,我们把这一条链上的点都新建一个分身并和原图保持一样的形状。

我们在每一次操作后都记录当前的根节点的编号即可。这就是传说中的可持久化,因为我们成功地保存了历史版本。

当然,这样的线段树不可能保持固定的形态,因此我们记录每一个点的左右儿子的信息。然后每次更新时再开新的节点,这就是动态开点(平衡树写多的话就完全没问题)


实现主席树

看板子题Luogu P3834 【模板】可持久化线段树 1(主席树)

首先值域线段树不能处理那么大的数字,因此我们先离散化

然后大体的做法上面都讲了,直接上CODE吧

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
const int N=2e5+5;
struct President_tree//President总统,不知道有没有主席的意思
{
    int lc,rc,sum;
}node[N<<6];
int n,m,q,a[N],b[N],l,r,k,rt[N],tot;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^‘-‘?1:-1;
    while (x=(x<<3)+(x<<1)+ch-‘0‘,isdigit(ch=tc())); x*=flag;
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+‘0‘);
}
inline int find(int x)
{
    int l=1,r=m;
    while (l<=r)
    {
        int mid=l+r>>1;
        if (b[mid]==x) return mid;
        if (b[mid]<x) l=mid+1; else r=mid-1;
    }
}
inline void build(int &now,int l,int r)//建树,注意动态开点
{
    now=++tot;
    if (l==r) return;
    int mid=l+r>>1;
    build(node[now].lc,l,mid); build(node[now].rc,mid+1,r);
}
inline int modify(int lst,int l,int r,int id)//更新某段路径的值
{
    int now=++tot; node[now]=node[lst]; ++node[now].sum;
    if (l==r) return now;
    int mid=l+r>>1;
    if (id<=mid) node[now].lc=modify(node[lst].lc,l,mid,id);
    else node[now].rc=modify(node[lst].rc,mid+1,r,id); return now;
}
inline int query(int u,int v,int l,int r,int k)//查询区间第k小,这里直接同时跳两条路径了
{
    int mid=l+r>>1,dlt=node[node[v].lc].sum-node[node[u].lc].sum;
    if (l==r) return l;
    if (dlt>=k) return query(node[u].lc,node[v].lc,l,mid,k);
    else return query(node[u].rc,node[v].rc,mid+1,r,k-dlt);
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); read(q);
    for (i=1;i<=n;++i)
    read(a[i]),b[i]=a[i];
    sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; build(rt[0],1,m);
    for (i=1;i<=n;++i)
    rt[i]=modify(rt[i-1],1,m,find(a[i]));
    for (i=1;i<=q;++i)
    {
        read(l); read(r); read(k);
        write(b[query(rt[l-1],rt[r],1,m,k)]); putchar(‘\n‘);
    }
    return 0;
}

实现可持久化数组

板子题:P3919 【模板】可持久化数组(可持久化线段树/平衡树)

这个就更加简单了,我们相当于维护一个可持久化线段树,并支持单点修改,单点查询即可

是不是听起来很ZZ,感觉完全不需要线段树

不过线段树起到了良好的存储信息+快速查找的左右,因此是不二选择(当然你要非旋Treap我也不想管你)

CODE

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
const int N=1e6+5;
struct Persistence_segtree//Persistence-这个是可持久化的意思
{
    int lc,rc,x;
}node[N*20];
int n,m,a[N],rt[N],opt,x,y,id,tot;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^‘-‘?1:-1;
    while (x=(x<<3)+(x<<1)+ch-‘0‘,isdigit(ch=tc())); x*=flag;
}
inline void write(int x)
{
    if (x<0) putchar(‘-‘),x=-x;
    if (x>9) write(x/10); putchar(x%10+‘0‘);
}
inline void build(int &now,int l,int r)
{
    now=++tot; if (l==r) { node[now].x=a[l]; return; }
    int mid=l+r>>1; build(node[now].lc,l,mid); build(node[now].rc,mid+1,r);
}
inline int modify(int lst,int l,int r,int id,int x)
{
    int now=++tot; node[now]=node[lst];
    if (l==r) { node[now].x=x; return now; } int mid=l+r>>1;
    if (id<=mid) node[now].lc=modify(node[lst].lc,l,mid,id,x);
    else node[now].rc=modify(node[lst].rc,mid+1,r,id,x); return now;
}
inline int query(int now,int l,int r,int id)
{
    if (l==r) return node[now].x; int mid=l+r>>1;
    if (id<=mid) return query(node[now].lc,l,mid,id);
    else return query(node[now].rc,mid+1,r,id);
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); read(m);
    for (i=1;i<=n;++i) read(a[i]); build(rt[0],1,n);
    for (i=1;i<=m;++i)
    {
        read(x); read(opt); read(id);
        if (opt^2) read(y),rt[i]=modify(rt[x],1,n,id,y);
        else write(query(rt[i]=rt[x],1,n,id)),putchar(‘\n‘);
    }
    return 0;
}

实现可持久化并查集

这个是最烦人的操作了吧,理论上只需要可持久化数组就可以搞了。

首先我们要摒弃掉平时的那些关于并查集的想法,在可持久化并查集中,不可以路径压缩。

那么还不GG,直接搞不是爆炸?

其实还有一个优化叫做按秩合并,也是启发式合并的一种,就是记录一下每个联通块的大小,然后每次合并的时候把小的并到大的上面去即可

复杂度最差\(O(n\ log n)\) ,完全不虚的好不好。

CODE

#include<cstdio>
#include<cctype>
using namespace std;
const int N=200005;
struct President_tree
{
    int ch[2],fa,dep;
}node[N*20];
int n,m,opt,x,y,rt[N],tot;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; while (!isdigit(ch=tc()));
    while (x=(x<<3)+(x<<1)+ch-‘0‘,isdigit(ch=tc()));
}
inline void swap(int &a,int &b)
{
    int t=a; a=b; b=t;
}
inline void build(int &now,int l,int r)
{
    now=++tot; if (l==r) { node[now].fa=l; return; }
    int mid=l+r>>1; build(node[now].ch[0],l,mid); build(node[now].ch[1],mid+1,r);
}
inline void insert(int lst,int &now,int l,int r,int x,int fa)
{
    now=++tot; if (l==r) { node[now].fa=fa; node[now].dep=node[lst].dep; return; }
    int mid=l+r>>1; node[now].ch[0]=node[lst].ch[0]; node[now].ch[1]=node[lst].ch[1];
    if (x<=mid) insert(node[lst].ch[0],node[now].ch[0],l,mid,x,fa);
    else insert(node[lst].ch[1],node[now].ch[1],mid+1,r,x,fa);
}
inline void updata(int now,int l,int r,int x)
{
    if (l==r) { ++node[now].dep; return; } int mid=l+r>>1;
    if (x<=mid) updata(node[now].ch[0],l,mid,x);
    else updata(node[now].ch[1],mid+1,r,x);
}
inline int query(int now,int l,int r,int x)
{
    if (l==r) return now; int mid=l+r>>1;
    if (x<=mid) return query(node[now].ch[0],l,mid,x);
    else return query(node[now].ch[1],mid+1,r,x);
}
inline int getfather(int t,int x)
{
    int fa=query(t,1,n,x);
    return x^node[fa].fa?getfather(t,node[fa].fa):fa;
}
inline void unionn(int t,int x,int y)
{
    rt[t]=rt[t-1]; int fx=getfather(rt[t],x),fy=getfather(rt[t],y);
    if (node[fx].fa==node[fy].fa) return;
    if (node[fx].dep<node[fy].dep) swap(fx,fy);
    insert(rt[t-1],rt[t],1,n,node[fy].fa,node[fx].fa);
    if (node[fx].dep==node[fy].dep) updata(rt[t],1,n,node[fx].fa);
}
inline void check(int t,int x,int y)
{
    rt[t]=rt[t-1]; int fx=getfather(rt[t],x),fy=getfather(rt[t],y);
    puts(node[fx].fa^node[fy].fa?"0":"1");
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); read(m); build(rt[0],1,n);
    for (i=1;i<=m;++i)
    {
        read(opt); read(x);
        switch (opt)
        {
            case 1:read(y),unionn(i,x,y);break;
            case 2:rt[i]=rt[x];break;
            case 3:read(y),check(i,x,y);break;
        }
    }
    return 0;
}

PS:刚开始swap写错了害我调了2H+

原文地址:https://www.cnblogs.com/cjjsb/p/9427046.html

时间: 2024-08-09 22:27:41

浅谈可持久化数据结构的相关文章

浅谈算法和数据结构

: 一 栈和队列 http://www.cnblogs.com/yangecnu/p/Introduction-Stack-and-Queue.html 最近晚上在家里看Algorithems,4th Edition,我买的英文版,觉得这本书写的比较浅显易懂,而且“图码并茂”,趁着这次机会打算好好学习做做笔记,这样也会印象深刻,这也是写这一系列文章的原因.另外普林斯顿大学在Coursera 上也有这本书同步的公开课,还有另外一门算法分析课,这门课程的作者也是这本书的作者,两门课都挺不错的. 计算

浅谈算法和数据结构(1):栈和队列

浅谈算法和数据结构(1):栈和队列 2014/11/03 ·  IT技术                                         · 2 评论                                      ·  数据结构, 栈, 算法, 队列 分享到: 60 SegmentFault D-Day 2015 北京:iOS 站 JDBC之“对岸的女孩走过来” CSS深入理解之relative HTML5+CSS3实现春节贺卡 原文出处: 寒江独钓   欢迎分享原创

浅谈算法和数据结构: 四 快速排序

原文:浅谈算法和数据结构: 四 快速排序 上篇文章介绍了时间复杂度为O(nlgn)的合并排序,本篇文章介绍时间复杂度同样为O(nlgn)但是排序速度比合并排序更快的快速排序(Quick Sort). 快速排序是20世纪科技领域的十大算法之一 ,他由C. A. R. Hoare于1960年提出的一种划分交换排序. 快速排序也是一种采用分治法解决问题的一个典型应用.在很多编程语言中,对数组,列表进行的非稳定排序在内部实现中都使用的是快速排序.而且快速排序在面试中经常会遇到. 本文首先介绍快速排序的思

浅谈算法和数据结构: 九 平衡查找树之红黑树

原文:浅谈算法和数据结构: 九 平衡查找树之红黑树 前面一篇文章介绍了2-3查找树,可以看到,2-3查找树能保证在插入元素之后能保持树的平衡状态,最坏情况下即所有的子节点都是2-node,树的高度为lgN,从而保证了最坏情况下的时间复杂度.但是2-3树实现起来比较复杂,本文介绍一种简单实现2-3树的数据结构,即红黑树(Red-Black Tree) 定义 红黑树的主要是像是对2-3查找树进行编码,尤其是对2-3查找树中的3-nodes节点添加额外的信息.红黑树中将节点之间的链接分为两种不同类型,

浅谈算法和数据结构: 十 平衡查找树之B树

转载自 http://www.cnblogs.com/yangecnu/p/3632027.html 浅谈算法和数据结构: 十 平衡查找树之B树 前面讲解了平衡查找树中的2-3树以及其实现红黑树.2-3树种,一个节点最多有2个key,而红黑树则使用染色的方式来标识这两个key. 维基百科对B树的定义为“在计算机科学中,B树(B-tree)是一种树状数据结构,它能够存储数据.对其进行排序并允许以O(log n)的时间复杂度运行进行查找.顺序读取.插入和删除的数据结构.B树,概括来说是一个节点可以拥

浅谈算法和数据结构: 五 优先级队列与堆排序

转载自:http://www.cnblogs.com/yangecnu/p/Introduce-Priority-Queue-And-Heap-Sort.html 浅谈算法和数据结构: 五 优先级队列与堆排序 在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象.最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话. 在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是

浅谈算法和数据结构系列汇总(转)

突然看到一个大神的系列文章讲的就是算法和数据结构,现在把它的文章集中分享给大家,向大神致敬: 浅谈算法和数据结构: 一 栈和队列 浅谈算法和数据结构: 二 基本排序算法 浅谈算法和数据结构: 三 合并排序 浅谈算法和数据结构: 四 快速排序 浅谈算法和数据结构: 五 优先级队列与堆排序 浅谈算法和数据结构: 六 符号表及其基本实现 浅谈算法和数据结构: 七 二叉查找树 浅谈算法和数据结构: 八 平衡查找树之2-3树 浅谈算法和数据结构: 九 平衡查找树之红黑树 浅谈算法和数据结构: 十 平衡查找

浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的时候具有较高的灵活性,而有序数组在查找时具有较高的效率,本文介绍的二叉查找树(Binary Search Tree,BST)这一数据结构综合了以上两种数据结构的优点. 二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构,后文会一一介绍. 一 定义 二叉查找树(B

浅谈算法和数据结构: 六 符号表及其基本实现

http://www.cnblogs.com/yangecnu/p/Introduce-Symbol-Table-and-Elementary-Implementations.html 浅谈算法和数据结构: 六 符号表及其基本实现 前面几篇文章介绍了基本的排序算法,排序通常是查找的前奏操作.从本文开始介绍基本的查找算法. 在介绍查找算法,首先需要了解符号表这一抽象数据结构,本文首先介绍了什么是符号表,以及这一抽象数据结构的的API,然后介绍了两种简单的符号表的实现方式. 一符号表 在开始介绍查找