[Data]可持久化线段树-主席树

可持久化线段树-主席树

主席树又称函数式线段树,顾名思义,就是通过函数实现的,也称可持久化线段树,支持在线处理,可以访问历史版本,后一刻可参考前一刻的状态,充分利用共同数据,

节省空间;主席树可以实现查询区间k小的问题等;

主席树的每个节点对应一棵线段树,对于一个序列,我们采用前缀和的思想,如a[1],a[2],a[3],.....,每个前缀建一棵线段树,那么就共有N棵线段树,两棵线段树之差得到的就是

某个区间数字出现的次数,插入一个值时,相当于新建一棵线段树,在前一棵线段树的基础上,新建logn个节点,其余的从前一棵线段树那里copy过来,线段树里维护的是在

这n个数的序列中,排名第K的数出现的次数,若要处理区间[l,r]问题,只需要利用前缀和思想,rt[r]-rt[l-1]就好了,rt[l-1]处理的是区间1-(l-1),rt[r]处理的是区间1-r,所以rt[r]-rt[l-1]处理的

就是区间l-r;

更新

首先需要建立一棵空线段树,rt数组里存的是第i棵线段树的根节点的编号,当我们插入一个值时,只需要修改从该节点到根的一条路径,若该节点进入线段树右子树,则左子树可以共用,若进入左子树,则右子树可以共用,直到到达叶子节点;

查询 区间第k[L,R]

思想为二分法,定义num=rt[now].val-rt[cmp].val,当k<=num时,直接往左子树查找,否则在右子树中查找,但注意num要减k,因为左子树肯定小于它了,直到L==R;

luogu 3834:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 200009
int cnt=0,a[maxn],s[maxn],ls[maxn*20],rs[maxn*20],val[maxn*20],rt[maxn],sz;
void build(int &p,int l,int r){
    p=++cnt;
    if(l!=r){
        int mid=(l+r)/2;
        build(ls[p],l,mid);
        build(rs[p],mid+1,r);
    }
}
void insert(int &p,int cmp,int x,int l,int r){
    p=++cnt;ls[p]=ls[cmp];rs[p]=rs[cmp];val[p]=val[cmp]+1;
    if(l!=r){
        int mid=(l+r)/2;
        if(x<=mid){
            insert(ls[p],ls[cmp],x,l,mid);
        }
        else{
            insert(rs[p],rs[cmp],x,mid+1,r);
        }
    }
}
int query(int l,int r,int L,int R,int k){
    if(L==R) return L;
    int num=val[ls[r]]-val[ls[l]],mid=(L+R)/2;
    if(k<=num) return query(ls[l],ls[r],L,mid,k);
    else return query(rs[l],rs[r],mid+1,R,k-num); 
}
int main()
{
    int n,m,l,r,k,i;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++){
        scanf("%d",&a[i]);
        s[i]=a[i];
    }
    sort(s+1,s+n+1);
    sz=unique(s+1,s+n+1)-s-1;
    build(rt[0],1,sz);
    for(i=1;i<=n;i++){
        insert(rt[i],rt[i-1],lower_bound(s+1,s+sz+1,a[i])-s,1,sz);
    }
    for(i=1;i<=m;i++){
        scanf("%d%d%d",&l,&r,&k);
        printf("%d
",s[query(rt[l-1],rt[r],1,sz,k)]);
    }
}
主席树

 

原文地址:https://www.cnblogs.com/Fish-/p/8167829.html