學習筆記:主席樹
阿新 • • 發佈:2021-07-16
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 現役