淺談可持久化數據結構

Preface

因爲我真的是太弱了,因此真的是淺談node

神奇的數據結構其實我也很虛啊!git


值域線段樹

簡單的說,值域線段樹區間裏面存的是在這個區間內的數的個數有多少個。數組

有沒有感受很簡單,考慮一下若是咱們有一棵這樣的線段樹,查找排名爲rk的數時只須要看一下左子樹的大小就能夠判斷在左邊仍是右邊了。數據結構

有沒有感受很像BSTide


動態開點與可持久化

根據上面的值域線段樹,咱們能夠得出一種naive的作法:優化

對於每個前綴\([1,i](i\in[1,n])\)都開一棵值域線段樹,而後查詢區間的時候直接每一個節點的size都變成\(size(r)-size(l-1)\),就是把前綴和的思想搬到了樹上ui

而後不只要MLE,每次複製線段樹都T了spa

咱們考慮優化一波,咱們發現每一次操做只會改變從根節點到葉子節點的一條路徑上點的值。code

而後因爲線段樹的深度是穩定且均勻\(logn\),所以咱們發現許多節點的信息都是能夠共用的:blog

咱們在修改紅色的這一條路徑的信息時,咱們把這一條鏈上的點都新建一個分身並和原圖保持同樣的形狀。

咱們在每一次操做後都記錄當前的根節點的編號便可。這就是傳說中的可持久化,由於咱們成功地保存了歷史版本。

固然,這樣的線段樹不可能保持固定的形態,所以咱們記錄每個點的左右兒子的信息。而後每次更新時再開新的節點,這就是動態開點(平衡樹寫多的話就徹底沒問題)


實現主席樹

看板子題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+

相關文章
相關標籤/搜索