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

【介绍】
主席树也就是函数式线段树,运用了可持久化思想从而可以在短时间寻找到一段区间的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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值