主席樹模板以及個人理解
阿新 • • 發佈:2020-10-25
主席樹模板及個人理解
主席樹的原理其他部落格已經寫的很詳細了,我就不加贅述了。
主要是記錄一下個人的部分理解以及模板,以便以後觀看
主席樹是一種可持久化資料結構,或者說,可持久化線段樹,利用主席樹
可以檢視線段樹的歷史狀態並對其修改
具體做法就是每個點開一顆線段樹
為了節省空間採取動態開點
建樹的時間複雜度是\(O(nlogn)\)的,單次查詢,插入都是\(O(logn)\)的
總複雜度為\(O((n+m)log n)\) m為操作次數
下面是模板
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #define ll long long using namespace std; /*主席樹為了節省空間,採取的是動態開點 與動態開點相同,l,r維護的是左右兒子下標 不是區間邊界 */ const int maxn=200010; int T; int n,m; int q; int tot; int cnt; int a[maxn]; int t[maxn]; int b[maxn]; struct node{ int l,r,sum; }tre[maxn<<5]; inline int read(){ int f=1; int ret=0; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-f; ch=getchar(); } while(ch<='9'&&ch>='0'){ ret=ret*10+(ch^'0'); ch=getchar(); } return f*ret; } /* 注意,build和update返回值是該樹的根 所以我們的rt不能用全域性變數 */ int build(int l,int r){ int rt=++cnt; if(l==r){ return rt; } int mid=(l+r)>>1; tre[rt].l=build(l,mid); tre[rt].r=build(mid+1,r); return rt; } /* 主席樹的精髓,每一個點建一顆線段樹 為了節省空間,對樹進行重複利用。 具體如何節省空間? 我們只開需要更改值的點 不需要更改值的點直接連上就好 */ int update(int t,int l,int r,int k){ int rt=++cnt; // cout<<rt<<endl; tre[rt].l=tre[t].l; tre[rt].r=tre[t].r; tre[rt].sum=tre[t].sum+1;//當前樹繼承上一顆樹的狀態 if(r==l){ return rt; } int mid=(l+r)>>1; if(k<=mid){ tre[rt].l=update(tre[t].l,l,mid,k);//更改需要更改的點 } else{ tre[rt].r=update(tre[t].r,mid+1,r,k); } return rt; } int query(int le,int re,int l,int r,int k){ if(l==r){ return l; } int x=tre[tre[re].l].sum-tre[tre[le].l].sum; int mid=(l+r)>>1; if(x>=k){ return query(tre[le].l,tre[re].l,l,mid,k); }//求區間第k大,因為維護的區間是有序的,所以加入左區間點數大於k,那麼區間第k大一定在左側 else{ return query(tre[le].r,tre[re].r,mid+1,r,k-x); } } int main(){ freopen("P3834_9.in","r",stdin); n=read(); q=read(); for(int i=1;i<=n;i++){ a[i]=read(); b[i]=a[i]; } m=unique(b+1,b+1+n)-b-1;//為什麼我們需要減1?陣列首元素需要考慮 sort(b+1,b+1+n); t[0]=build(1,m); for(int i=1;i<=m;i++){ a[i]=lower_bound(b+1,b+1+m,a[i])-b; t[i]=update(t[i-1],1,m,a[i]); } int x,y,z; while(q--){ x=read(); y=read(); z=read(); int p=query(t[x-1],t[y],1,m,z); cout<<b[p]<<endl; } return 0; }