主席树简明教程

在线段树的实际应用中,我们经常要访问线段树的历史版本。

这时候,我们就需要一种新的数据结构:主席树(别问我为什么叫主席树,去问主席)。

 

由于之前写了半天的消失了,那我就写个简洁点的。 

 

为了保存线段树的历史版本,我们可以每修改一次就复制整棵线段树。

但是这样做空间和时间都承受不下,然后我们会发现每一次修改事实上只影响了一部分节点。

那么我们对于每次修改,就可以只复制一遍影响到的节点并且进行修改,其他的节点共用。

每次都会新建一个根,记录每个版本的线段树的根节点地址就可以找到这个版本。

如图:

图就建成这样,然后要访问历史版本的时候找到那个根就好了。

最终对于一个有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 ;
}

 

  

 

总结一下,这个数据结构就是靠指针来维持,主要思想就是只复制并修改一次操作影响到的的节点。

对于其他节点和指针,都不进行修改,不同版本之间可能会共用一些节点,从而减少时空复杂度。

posted @ 2018-01-29 15:08  Destinies  阅读(339)  评论(1编辑  收藏  举报