题解 P3834 【【模板】可持久化线段树 1(主席树)】

· · 题解

今天刚学完主席树,个人觉得还是比较简单的

#include<cstdio>
#include<algorithm>
using namespace std;
const int M=(2e5)+10;
#define ls tree[now].l
#define rs tree[now].r

int n,m;
int k,N;
int root[M];
int a[M],ys[M];
//a存离散化后的数,ys建立离散化后与原数的映射关系,root为森林的不同根节点 

struct Node{
    int num;
    int id;
}q[M];

struct node{
    int l,r;
    int size;
}tree[M<<5];//多开 1<<5 的空间 

inline int read(){
    int f=1,x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

bool cmp(Node x,Node y){//求第k小的数,所以从小到大排序 
    return x.num<y.num;
}

void lsh(){//离散化模板 
    a[q[1].id]=++N;
    ys[N]=q[1].num;//注意存映射的点 
    for(int i=2;i<=n;i++){
        if(q[i].num!=q[i-1].num) N++;
        a[q[i].id]=N;
        ys[N]=q[i].num;
    }
}

int build(int l,int r){//动态建树,防止数组越界或开不够 
    int now=++k;//加点 
    if(l<r){
        int mid=(l+r)>>1;
        ls=build(l,mid);//更新左儿子 
        rs=build(mid+1,r);//更新右儿子 
    }
    return now;
}

int update(int pre,int l,int r,int x){//建权值线段树 
    int now=++k;
    tree[now].size=tree[pre].size+1;//此线段树的点数为上一棵的点数+1 
    ls=tree[pre].l;
    rs=tree[pre].r;
    //继承上一棵线段树 
    if(l<r){//寻找需要更新的链 
        int mid=(l+r)>>1;
        if(x>mid) rs=update(rs,mid+1,r,x);//在右儿子更新 
        else ls=update(ls,l,mid,x);//在左儿子更新 
    }
    return now;
}

int query(int u,int v,int l,int r,int x){//查询操作 
    if(l==r) return l;//找到第k小值 
    //第r棵线段树左儿子-第(l-1)棵线段树左儿子的值 
    int t=tree[tree[v].l].size-tree[tree[u].l].size;
    int mid=(l+r)>>1;
    if(x<=t) return query(tree[u].l,tree[v].l,l,mid,x);//在左儿子上 
    else return query(tree[u].r,tree[v].r,mid+1,r,x-t);//在右儿子上 
}

int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++){
        q[i].num=read();
        q[i].id=i;
    }
    sort(q+1,q+n+1,cmp);
    lsh();
    root[0]=build(1,N);//建一颗空的线段树 
    for(int i=1;i<=n;i++)//建n棵线段树,边加点边建树 
        root[i]=update(root[i-1],1,N,a[i]);
    for(int i=1,l,r,x;i<=m;i++){
        l=read();r=read();x=read();
        printf("%d\n",ys[query(root[l-1],root[r],1,N,x)]);
        //[l,r]就等价于 第r棵线段树-第(l-1)棵线段树 的k小值,返回该节点映射的值 
    }
    return 0;
}