静态主席树学习笔记

主席树相较于普通的线段树而言,它的最大特点在于:动态开点(省空间);

静态主席树的经典题目是[P3834] 静态区间第k小;

具体做法是先建一棵权值线段树,然后对于1~n这个区间,以[1,ai]为一棵树的范围建立一棵权值线段树,这样就建立了n+1棵权值线段树;

而除了一开始建立的第一棵树外,后面新建的n棵树的节点都是不全的,它们中的一些节点都是借用了它们前面的树的节点,这样子就节省了空间来让我们能够去开多棵线段树;

注意:在模板里,第 k小也会把相同的数给算进去的,比如:3 3 4 4 5,在区间[1,4]里,第 2小不是 4,而是第二个 3;

下面附上代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#define ll long long
using namespace std;
const int N = 2e6+10;

inline int read(){
    int ref=0,x=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
    while(isdigit(ch)){ref=ref*10+ch-'0';ch=getchar();}
    return ref*x;
}

int n,m,tot;
ll a[N],b[N];
ll T[N<<4],st[N<<4],ls[N<<4],rs[N<<4];                    /*T[i] 表示第 i棵线段树的根节点的编号*/
                                                          /*st[i] 表示 i这个编号的节点的值是多少*/ 

ll build(ll l,ll r){
    ll rt=++tot;
    if(l<r){
        ll mid=(l+r)>>1;
        ls[rt]=build(l,mid);
        rs[rt]=build(mid+1,r);
    }
    return rt;
}

ll updata(ll pre,ll l,ll r,ll x){                         /* rt->pre */
    ll rt=++tot; 
    ls[rt]=ls[pre],rs[rt]=rs[pre],st[rt]=st[pre]+1;       /*将新的 rt节点的儿子接向上一棵线段树节点 pre的儿子*/
                                                          /*也就是先继承*/
    if(l<r){
        ll mid=(l+r)>>1;
        if(x<=mid) ls[rt]=updata(ls[pre],l,mid,x);        /*再把需要修改的部分给接回到新开的线段树,而不需要修改的部分则继续和上一棵线段树同穿一条裤子*/
        else rs[rt]=updata(rs[pre],mid+1,r,x);            /*这就是主席树的精髓之处——省空间(同穿一条裤子)*/ 
    }
    return rt;
}

ll query(ll L,ll R,ll l,ll r,ll k){                       /* L表示左边的线段树的根节点, R表示右边的线段树的根节点*/
    if(l>=r) return l;
    ll x=st[ls[R]]-st[ls[L]];
    ll mid=(l+r)>>1;
    if(x>=k) return query(ls[L],ls[R],l,mid,k);
    else return query(rs[L],rs[R],mid+1,r,k-x);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) a[i]=read(),b[i]=a[i];
    sort(b+1,b+1+n);
    int len=unique(b+1,b+1+n)-b-1;                        /*去重后的数组长度*/
    T[0]=build(1,len);
    for(int i=1;i<=n;i++){
        int temp=lower_bound(b+1,b+len+1,a[i])-b;         /*在 b中查找第一个 >=a[i]的数的位置,temp就是 a的 rank*/
                                                          /*离散化有 2种方法,这种二分法的好处是容易处理带有重复的数的数组*/
        T[i]=updata(T[i-1],1,len,temp);
    }
    for(int i=1;i<=m;i++){
        int x,y,z;
        x=read(),y=read(),z=read();
        ll t=query(T[x-1],T[y],1,len,z);
        printf("%d\n",b[t]);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/nnezgy/p/11379701.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值