主席树/可持久化线段树总结

来源:互联网 发布:多店铺管理系统 源码 编辑:程序博客网 时间:2024/05/21 10:32

【介绍】
主席树也就是函数式线段树,运用了可持久化思想从而可以在短时间寻找到一段区间的K大值,是一个非常优秀的算法,但是属于离线算法。

【思想】
对于给定的一个序列,每次询问某一段自区间的K大值。对于这类题型,就是主席树登场的时候,首先对这个序列离散,假设去重后有x个元素,那么用线段树来维护区间中数的个数。对于询问的某一段区间[L,R],利用容斥思想,用R的前缀减去L的前缀便是答案。所以要分别对序列0~k(0<=k<=x)的元素构造大小相同的线段树。然后类似于平衡树的思想求K大值。但是构造n棵线段树的空间是O(2x2)(Ps:这是对于指针记录左右儿子的线段树,一般线段树大家都知道是O(4x2)),对于x达到10的6,7次的题目完全不适用。

进一步可以发现,其实构造好[0,k](0<=k<x)的线段树后,可以对于构造[0,k+1]的线段树只需要修改一些节点的数值,并非全部,所以并不需要重新构造一颗线段树,只需要在原先线段树的基础上插入一些修改后的节点共用一些子节点就行了,如下图:

这里写图片描述

这样可以实现可持久化,降低了空间乃至时间复杂度。
对于数组的大小也就是log2(N)*N(N表示元素个数,因为线段树的深度最多也就是log2(N))。

【核心代码】

struct PT{    PT* son[2];    int l,r,s;}tem[maxm],*Null=tem,*len=Null,*ro[maxn];void Pushup(PT* k) {k->s=k->son[0]->s+k->son[1]->s;}PT* New(int L,int R,int p)//构造新节点 {    ++len; len->l=L; len->r=R; len->s=p; len->son[0]=len->son[1]=Null; return len;}PT* Build(int L,int R)//建树 {    PT* now=New(L,R,0);    if (L==R) return now;    int mid=(R-L>>1)+L;    now->son[0]=Build(L,mid); now->son[1]=Build(mid+1,R);    Pushup(now); return now;}PT* Insert(PT* k,int where)//插入 {    int L=k->l,R=k->r;    PT* now=New(L,R,k->s);    now->son[0]=k->son[0]; now->son[1]=k->son[1];    if (L==R) {now->s++; return now;}    int mid=(R-L>>1)+L;    if (where<=mid) now->son[0]=Insert(now->son[0],where);     else now->son[1]=Insert(now->son[1],where);    Pushup(now); return now;}int Query(PT* L,PT* R,int k)//询问K大值,类似平衡树思想 {    if (L->l==L->r) return b[L->l];    int p=R->son[0]->s-L->son[0]->s;//容斥思想     if (p>=k) return Query(L->son[0],R->son[0],k);     else return Query(L->son[1],R->son[1],k-p);}