主席树简明教程
在线段树的实际应用中,我们经常要访问线段树的历史版本。
这时候,我们就需要一种新的数据结构:主席树(别问我为什么叫主席树,去问主席)。
由于之前写了半天的消失了,那我就写个简洁点的。
为了保存线段树的历史版本,我们可以每修改一次就复制整棵线段树。
但是这样做空间和时间都承受不下,然后我们会发现每一次修改事实上只影响了一部分节点。
那么我们对于每次修改,就可以只复制一遍影响到的节点并且进行修改,其他的节点共用。
每次都会新建一个根,记录每个版本的线段树的根节点地址就可以找到这个版本。
如图:
图就建成这样,然后要访问历史版本的时候找到那个根就好了。
最终对于一个有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('\n');
}
return ;
}
总结一下,这个数据结构就是靠指针来维持,主要思想就是只复制并修改一次操作影响到的的节点。
对于其他节点和指针,都不进行修改,不同版本之间可能会共用一些节点,从而减少时空复杂度。

浙公网安备 33010602011771号