主席树学名可持久化线段树,就是这个可持久化,衍生了多少数据结构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; }
这就是主席树,是否是很简单。
有人也许会问,知道单点修改的主席树怎么写了,区间修改的怎么写呢?
它的本质是同样的,只须要把修改的值作一个永久标记在它的祖先们身上,而后求交就能够了
这篇文章对你有没有帮助呢?有的话,点个赞吧。
若是有什么不满意的地方,欢迎在评论区反馈