主席樹學名可持久化線段樹,就是這個可持久化,衍生了多少數據結構ios
爲何會有主席樹這個數據結構呢?它被髮明是用來解決什麼問題的呢?git
給定n個數,m個操做,操做類型有在某個歷史版本下單點修改,輸出某個歷史版本下某個位置的值的值,n和m小於等於1e6數據結構
乍一看是否是一點頭緒也沒有。咱們先來想一想暴力怎麼作,暴力存儲第i個狀態下每一個數的值,顯然這樣作不是TLE就是MLE,咱們不妨管這種狀態叫作TM雙LE。優化
若是沒有這個歷史狀態顯然處理很簡單,一個線段樹就解決了。那麼加上歷史狀態呢?若是咱們優化一下暴力,咱們會發現咱們能夠建若干棵樹,一棵樹存儲一個狀態下的全部信息。ui
顯然這種處理方式還不如剛纔呢,狀態的轉移依然很慢,MLE也更加嚴重了,因此咱們仍是TM雙LE。怎麼辦呢?咱們要想辦法加快轉移,同時優化空間,二者要同時作到彷佛有點難,這個時候就要用到主席樹了。spa
主席樹是怎麼維持可持久化的呢?跟上面說的同樣建若干棵樹,第i棵樹表示第i次操做後的狀態。咱們會發現,在每次修改時,兩個子節點中只有一個會被修改,也就是說一次修改只會有logn個節點被修改,那麼顯然全部節點都新建備份是又慢又浪費的。咱們可讓修改後的樹跟修改前的樹共享節點,大大節省了時間和空間,這道題就作完了。code
這是題面get
那麼直接上代碼吧string
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cctype> #define ll long long #define gc getchar #define maxn 1000005 using namespace std; inline ll read(){ ll a=0;int f=0;char p=gc(); while(!isdigit(p)){f|=p=='-';p=gc();} while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();} return f?-a:a; }int n,m,a[maxn]; struct ahaha{ int v,ch[2]; }t[maxn*20];int cnt,num,rt[maxn]; #define lc t[i].ch[0] #define rc t[i].ch[1] #define Lc t[j].ch[0] #define Rc t[j].ch[1] void build(int &i,int l,int r){ i=++num; if(l==r){t[i].v=a[l];return;} int m=l+r>>1; build(lc,l,m);build(rc,m+1,r); } void update(int &i,int j,int l,int r,int k,int z){ i=++num;lc=Lc;rc=Rc; //共用一個子節點節省空間,加快速度 if(l==r){t[i].v=z;return;} int m=l+r>>1; if(k<=m)update(lc,Lc,l,m,k,z); else update(rc,Rc,m+1,r,k,z); } int query(int i,int l,int r,int k){ if(l==r)return t[i].v; int m=l+r>>1; if(k<=m)return query(lc,l,m,k); return query(rc,m+1,r,k); } inline void solve_1(int k){ int x=read(),z=read(); update(rt[++cnt],rt[k],1,n,x,z); } inline void solve_2(int k){ int x=read();rt[++cnt]=rt[k]; printf("%d\n",query(rt[cnt],1,n,x)); } int main(){ n=read();m=read(); for(int i=1;i<=n;++i) a[i]=read(); build(rt[0],1,n); //先把第0版本的樹建出來 while(m--){ int k=read(),zz=read(); switch(zz){ case 1:solve_1(k);break; case 2:solve_2(k);break; } } return 0; }
提到主席樹,想必各位最早想到的仍是區間第k大it
區間第k大是怎麼利用可持久化的呢?
首先說一下什麼是權值線段樹。日常的線段樹下標是表示第幾個數,權值線段樹的下標是表明數字的值,那麼節點的權值就是表明數字出現的次數。
那麼維護區間第k大就須要建n棵權值線段樹,第i棵樹維護的是區間\([1,i]\)中每一個數出現的次數
很顯然用剛纔的方法維護就ok了
上代碼
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cctype> #define ll long long #define gc getchar #define maxn 200005 using namespace std; inline ll read(){ ll a=0;int f=0;char p=gc(); while(!isdigit(p)){f|=p=='-';p=gc();} while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();} return f?-a:a; }int n,m,cnt,a[maxn],b[maxn]; struct ahaha{ int v,ch[2]; }t[maxn*20];int num,rt[maxn]; #define lc t[i].ch[0] #define rc t[i].ch[1] #define Lc t[j].ch[0] #define Rc t[j].ch[1] void update(int &i,int j,int l,int r,int k){ i=++num;t[i]=t[j];++t[i].v; if(l==r)return; int m=l+r>>1; if(k<=m)update(lc,Lc,l,m,k); else update(rc,Rc,m+1,r,k); } int query(int i,int j,int l,int r,int k){ if(l==r)return l; int m=l+r>>1,v=t[Lc].v-t[lc].v; if(k<=v)return query(lc,Lc,l,m,k); return query(rc,Rc,m+1,r,k-v); } inline void solve(){ int x=read(),y=read(),k=read(); printf("%d\n",b[query(rt[x-1],rt[y],1,cnt,k)]); //別忘了要求輸出的是原數,別把離散化後的值輸出了 } int main(){ n=read();m=read(); for(int i=1;i<=n;++i) //先要離散化,不然無法存 a[i]=b[i]=read(); sort(b+1,b+n+1);cnt=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;++i) //建n棵權值線段樹 update(rt[i],rt[i-1],1,cnt,lower_bound(b+1,b+cnt+1,a[i])-b); while(m--) solve(); return 0; }
這就是主席樹,是否是很簡單。
有人也許會問,知道單點修改的主席樹怎麼寫了,區間修改的怎麼寫呢?
它的本質是同樣的,只須要把修改的值作一個永久標記在它的祖先們身上,而後求交就能夠了
這篇文章對你有沒有幫助呢?有的話,點個贊吧。
若是有什麼不滿意的地方,歡迎在評論區反饋