主席树介绍 : 洛谷P3834 可持久化线段树 1(主席树)

26 篇文章 0 订阅
20 篇文章 0 订阅

主席树介绍 : 洛谷P3834 可持久化线段树 1(主席树)

昨天开始进入主席树的学习。。

首先,主席树也是线段树的一种,但是空间比线段树更大,这是因为,他记录了每一次修改之前的状态,也就是说,他比一般的线段树有更多的节点,一开始看到有题解说,每一个节点都存了一个线段树,这把我吓了一跳,其实准确来说,应该不是这样的,下面看我讲解:

主席树,是在权值线段树上发展来的,对于每一个 L == R 的节点,其保存的值是 L 存在多少个

也就是说,我们每一次更新,都是修改值的个数。那如何来修改呢,将原来版本的线段树整个copy出来是肯定不可行的,那我们来看看新的线段树和旧的线段树有哪些共同之处:

我们对每一个值的修改,也就是说,我们只需要对包含这个值的区间进行修改即可,原来不需要修改的节点,我们依然可以使用,于是就将修改过后的父亲节点连在了原来的子节点上,对于需要修改的节点,我们就新建一个节点代替它。

一颗主席树就是这样构成的

下面我推荐另一个博主的博客(因为有图啊!有图有真相!是我太懒不想画图):https://blog.csdn.net/pengwill97/article/details/80920143

接下来,我们回到这道题上来,这是一道非常经典的题目,大意是:给你一串数,然后再给你多个区间,求区间第k大的数。一道一样的题目(注意范围不同)HDU2665(http://acm.hdu.edu.cn/showproblem.pp?pid=2665)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e5+1,LOG=20;
int n,m,tot=0,len;
int a[maxn],b[maxn],tree[maxn],L[maxn*LOG],R[maxn*LOG],sum[maxn*LOG];//O(nlogn + mlogn)主席树的空间复杂度
int build(int l,int r){
    int root=++tot;
    if(l<r){
        int mid=(l+r)>>1;
        L[root]=build(l,mid);
        R[root]=build(mid+1,r);
    }
return root;
}
void discrete(){
    sort(b+1,b+1+n);
    len=unique(b+1,b+1+n)-b-1;
    tree[0]=build(1,len);//先建一课空树(所有值为0)只有节点编号存在
}
int update(int pre,int l,int r,int x){//pre=上一个主席树的根节点,l和r为当前节点的区间,x为插入的值
    int root=++tot;
    L[root]=L[pre],R[root]=R[pre],sum[root]=sum[pre]+1;
    //先初始化一下,L代表root节点的左节点的编号,R同理,sum代表当前节点里的数的个数(插入进来肯定要+1)
    if(l<r){
        int mid=(l+r)>>1;
        if(x<=mid)//看插入的数所在的区间,对应区间需要新建一个节点,作为当前节点儿子,不需要的当然不用修改了
            L[root]=update(L[pre],l,mid,x);
        else
            R[root]=update(R[pre],mid+1,r,x);
    }
return root;
}
int query(int fr,int to,int l,int r,int k){
    if(l==r) return l;//反正一直查到底就是答案了
    int x=sum[L[to]]-sum[L[fr]];//算出这个区间的左边在下有多少个元素
    int mid=(l+r)>>1;
    if(k>x)  return query(R[fr],R[to],mid+1,r,k-x);//看是否需要查询右儿子
    else return query(L[fr],L[to],l,mid,k);
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),b[i]=a[i];
    discrete();//离散化
    for(int i=1;i<=n;i++){
        int num=lower_bound(b+1,b+1+len,a[i])-b;
        tree[i]=update(tree[i-1],1,len,num);//将离散化后的数插入主席树,每一次插入都是一次更新
    }
    while(m--){
        int le,ri,k;
        scanf("%d %d %d",&le,&ri,&k);
        if(le>ri)   swap(le,ri);
        int ans=query(tree[le-1],tree[ri],1,len,k);//有点类似于前缀和
        //因为我们是按照原序列的顺序update的,所以我们只需要将两颗不同状态的线段树相减,即可得出区间序列
        //然后又因为是权值线段树,所以我们只需要根据所得线段树的个数来求解即可
        printf("%d\n",b[ans]);
    }
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值