在线段树的实际应用中,我们经常要访问线段树的历史版本。
这时候,我们就需要一种新的数据结构:主席树(别问我为什么叫主席树,去问主席)。
由于之前写了半天的消失了,那我就写个简洁点的。
为了保存线段树的历史版本,我们可以每修改一次就复制整棵线段树。
但是这样做空间和时间都承受不下,然后我们会发现每一次修改事实上只影响了一部分节点。
那么我们对于每次修改,就可以只复制一遍影响到的节点并且进行修改,其他的节点共用。
每次都会新建一个根,记录每个版本的线段树的根节点地址就可以找到这个版本。
如图:
图就建成这样,然后要访问历史版本的时候找到那个根就好了。
最终对于一个有n个数的序列,会建立n棵共用部分节点的线段树,编号为i的树中有序列中前i个数的信息。
那么假如要询问一个区间的某些信息,只要用前缀和的思想两端相减即可得到(权值线段树)。
//洛谷P3834主席树模板的代码 int build(int L,int R){ int num=cnt++; sum[num]=0; if(L>=R)return num; lch[num]=build(L,mid); rch[num]=build(mid+1,R); return num; }//最开始的表示前0个数的线段树 int insert(int pre,int L,int R,int x){ int num=cnt++; sum[num]=sum[pre]+1;if(L>=R)return num; lch[num]=lch[pre],rch[num]=rch[pre]; if(x<=mid)lch[num]=insert(lch[num],L,mid,x); else rch[num]=insert(rch[num],mid+1,R,x); return num; }//这里使用权值线段树,在前一棵线段树的基础上插入一个x int query(int u,int v,int L,int R,int k){ if(L>=R)return L; int cnt=sum[lch[v]]-sum[lch[u]]; if(cnt<k)return query(rch[u],rch[v],mid+1,R,k-cnt); else return query(lch[u],lch[v],L,mid,k); } void init(){ n=read(),q=read(); for(int i=1;i<=n;i++)a[i]=b[i]=read(); sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1;//排序去重 rt[0]=build(1,m); for(int i=1;i<=n;i++){ int temp=lower_bound(b+1,b+m+1,a[i])-b; rt[i]=insert(rt[i-1],1,m,temp);//记录每一棵线段树的根节点编号 } return ; } void work(){ for(int i=1;i<=q;i++){ int opl=read(),opr=read(),k=read(); int u=query(rt[opl-1],rt[opr],1,m,k); write(b[u]),putchar(' '); } return ; }
总结一下,这个数据结构就是靠指针来维持,主要思想就是只复制并修改一次操作影响到的的节点。
对于其他节点和指针,都不进行修改,不同版本之间可能会共用一些节点,从而减少时空复杂度。