1. 程式人生 > 其它 >學習筆記:主席樹

學習筆記:主席樹

What is Zhu Xi Shu?

  主席樹是可持久化資料結構的一種,他以線段樹為原型,除了支援線段樹常有的操作外,還支援對歷史版本的查詢功能。一般被用於可持久化陣列/棧,或者就直接作為可持久化線段樹使用。

How does it work?

  對於要查詢歷史版本這一操作來說,最樸素的想法是每次修改後都重建一個新版本,這樣的空間複雜度是\(O(n*m)\)\(m\)是操作次數,\(n\)是資料範圍。
  考慮優化。可以發現,對於每一次修改,只會有從根到被修改節點的路徑上的\(logn\)個節點發生改變,其餘不變。
  所以考慮對不變的節點新樹與原數共用,然後對發生修改的結點新建節點。空間複雜度\(O(n+m*logn)\)


  然後對於線段樹原有的操作正常進行即可,只是要呼叫對應版本的入口,通過呼叫入口,我們可以訪問任一歷史版本。

How to code?(標程,指標實現)

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    const int SIZE=1e6+4;
    int n,m,cnt;
    int a[SIZE];
    struct node{int val;node *l,*r;}root[SIZE];
    void insert(node &before,node &now,int l,int r,int pos,int val)
    {   
        if(l==r){now.val=val;return;}
        int mid=(l+r)>>1;
        if(pos<=mid)
        {
            now.r=before.r;
            now.l=new node;
            insert(*before.l,*now.l,l,mid,pos,val);
        }
        else
        {
            now.l=before.l;
            now.r=new node;
            insert(*before.r,*now.r,mid+1,r,pos,val);
        }
    }
    int query(node &before,node &now,int l,int r,int pos)
    {
        if(l==r)
        {
            now.val=before.val;
            return now.val;
        }
        int mid=(l+r)>>1;
        now.l=before.l;
        now.r=before.r;        
        if(pos<=mid)
            return query(*before.l,*now.l,l,mid,pos);
        else
            return query(*before.r,*now.r,mid+1,r,pos);
    }
    void build(node &now,int l,int r)
    {
        if(l==r){now.val=a[l];return;}
        int mid=(l+r)>>1;
        now.l=new node;
        now.r=new node;
        build(*now.l,l,mid),build(*now.r,mid+1,r);
    }
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read*10)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
};
using namespace STD;
int main()
{
    n=read(),m=read();
    for(rr int i=1;i<=n;i++) a[i]=read();
    build(root[0],1,n);
    while(m--)
    {
        int id=read(),op=read(),pos=read();
        if(op==1)
        {
            int val=read();
            insert(root[id],root[++cnt],1,n,pos,val);
        }
        if(op==2)
        {
            int ans=query(root[id],root[++cnt],1,n,pos);
            printf("%d\n",ans);
        }
    }
}

  這裡實際上是洛谷板子題的程式碼,傳送門

Expansion(拓展應用)

可持久化陣列。

  其實就是上面的程式碼。。。。。。。(逃)

可持久化棧

  還是上面的程式碼,只是要加一個數組記錄每一個狀態對應的棧頂位置。

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    const int SIZE=5e5+4;
    int n;
    int top[SIZE];
    int fa[SIZE],to[SIZE];
    int dire[SIZE],head[SIZE];
    double val[SIZE],depth[SIZE];
    double ans[SIZE];
    inline void add(int f,int t)
    {
        static int num1=0;
        to[++num1]=t;
        dire[num1]=head[f];
        head[f]=num1;
    }
    namespace Prisident_tree
    {
        struct node
        {
            int id;
            node *l,*r;
        }root[SIZE];
        void build(node &now,int l,int r)
        {
            if(l==r) {if(l==1) now.id=1;return;}
            rr int mid=(l+r)>>1;
            now.l=new node;
            now.r=new node;
            build(*now.l,l,mid),build(*now.r,mid+1,r);
        }
        void insert(node &before ,node &now,int l,int r,int pos,int id)
        {
            if(l==r){now.id=id;return;}
            rr int mid=(l+r)>>1;
            if(pos<=mid)
            {
                now.r=before.r;
                now.l=new node;
                insert(*before.l,*now.l,l,mid,pos,id);
            }
            else 
            {
                now.l=before.l;
                now.r=new node;
                insert(*before.r,*now.r,mid+1,r,pos,id);
            }
        }
        int query(rr node now,rr int l,rr int r,rr int pos)
        {
            if(l==r) return now.id;
            rr int mid=(l+r)>>1;
            if(pos<=mid) 
                return query(*now.l,l,mid,pos);
            else 
                return query(*now.r,mid+1,r,pos);
        }  
    };
    using namespace Prisident_tree;
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }   
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read*10)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    inline double rate(rr int id1,rr int id2){return (val[id1]-val[id2])/(depth[id1]-depth[id2]);}
    int find(int f,int now)
    {
        int l=1,r=top[f];
        rr double a1,a2;
        while(l<r)
        {
            rr int mid=(l+r)>>1;
            rr int id1=query(root[f],1,n,mid);
            rr int id2=query(root[f],1,n,mid+1);
            a1=rate(id2,id1);
            a2=rate(now,id2);
            if(a1>=a2) r=mid;
            else l=mid+1;
        }
        return l;
    }
    void dfs(int now)
    {
        if(depth[now]==1.00)
        {
            ans[now]=rate(now,1);
            insert(root[fa[now]],root[now],1,n,2,now);
            top[now]=2;
        }
        else
        {
            if(now!=1)
            {
                int pos=find(fa[now],now);
                top[now]=pos+1;
                int id=query(root[fa[now]],1,n,pos);
                ans[now]=rate(now,id);
                insert(root[fa[now]],root[now],1,n,top[now],now);
            }
        }
        for(rr int i=head[now];i;i=dire[i])
        {
            depth[to[i]]=depth[now]+1.00;
            dfs(to[i]);
        }
    }
};
using namespace STD;
int main()
{
    n=read();
    for(rr int i=1;i<=n;i++) int x=scanf("%lf",val+i);
    for(rr int i=2;i<=n;i++) fa[i]=read(),add(fa[i],i);
    build(root[1],1,n);
    dfs(1);
    for(rr int i=2;i<=n;i++) printf("%.10lf\n",-ans[i]);
}

  這裡實際上是一道名叫Lost My Music的題的AC程式碼,裡面的主席樹就是用來維護可持久化棧的,並且採用二分退棧。
  題面自己搜吧,具體思路請看我上一篇部落格。
2021.7.16 現役