主席树 poj 2104 K-th Number

主席树又名可持久化线段树。

我们先撇开可持久化看看主席树是怎么实现的。维护一个长度为n的序列,我们可以建一颗线段树来维护,最上的根节点代表的区间是[1,n]。

我们知道线段树的形态由你给的[l,r],既维护的区间长度决定,现在我要为这个序列的每一个前缀建一棵线段树,每一棵线段树最上的根节点代表的区间依然是[1,n],所以这n棵线段树的结构是一模一样的。

这里的线段树不是普通的线段树而是权值线段树,既它维护的不是序列里的每个ai的值而是整个序列的第几大。对每个ai,插入树中不是ai值而是它是第几大v,接下来从根节点开始v与对应区间的mid比较,小于等于mid就划到左子树,否则划到右子树,直到叶子节点。整个过程像极了二叉搜索树,可以类比理解。

说完建树我们来看看如何实现可持久化。根据clj的论文,可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗。那么哪些地方可以“充分利用它们之间的共同数据”,第i棵线段树建树过程中一个v如果插到左子树,那么当前结点的右子树和第i-1棵线段树对应节点是一模一样的,所以我们只需要把第i棵线段树当前结点的右子树指向第i-1棵线段树对应节点就行了。如果插入右边,同理。这样就实现了可持久化。

下面是几个重要结论:

1、建第i棵线段树,只建了logn个节点,因为每次插入操作只产生一个节点另一个节点是复用的。

2、建树过程中第i棵线段树只与第i-1棵线段树有关。


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;
const int maxn=1e5+7;

struct node{
    int ls,rs,sum;
}data[maxn*20];
int cnt;
int root[maxn];
int a[maxn],b[maxn],mp[maxn];
int build(int l,int r){
    int t=cnt++;
    data[t].sum=0;
    if(l==r)return t;
    int mid=l+r>>1;
    data[t].ls=build(l,mid);
    data[t].rs=build(mid+1,r);
    return t;
}
int update(int k,int l,int r,int v){
    int t=cnt++;
    data[t].sum=data[k].sum+1;
    if(l==r)return t;
    int mid=l+r>>1;
    if(v<=mid){
        data[t].rs=data[k].rs;
        data[t].ls=update(data[k].ls,l,mid,v);
    }
    else {
        data[t].ls=data[k].ls;
        data[t].rs=update(data[k].rs,mid+1,r,v);
    }
    return t;
}
int query(int t1,int t2,int l,int r,int v){
    if(l==r)return l;
    int mid=l+r>>1;
    int w=data[data[t2].ls].sum-data[data[t1].ls].sum;
    if(v<=w) return query(data[t1].ls,data[t2].ls,l,mid,v);
    else return query(data[t1].rs,data[t2].rs,mid+1,r,v-w);
}

void debug(int t,int l,int r){
    printf("l==%d r==%d sum==%d\n",l,r,data[t].sum);
    if(l==r)return ;
    int mid=l+r>>1;
    debug(data[t].ls,l,mid);
    debug(data[t].rs,mid+1,r);
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        cnt=1;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        sort(b+1,b+n+1);
        int len=unique(b+1,b+1+n)-(b+1);
        for(int i=1;i<=n;i++){
            int t=lower_bound(b+1,b+1+len,a[i])-(b+1)+1;
            mp[t]=a[i];a[i]=t;
        }
        root[0]=build(1,len);
        for(int i=1;i<=n;i++){
            root[i]=update(root[i-1],1,len,a[i]);
        }
        /*for(int i=0;i<=n;i++){
            printf("----i==%d------\n",i);
            debug(root[i],1,len);
        }*/
        for(int i=0;i<m;i++){
            int l,r,v;
            scanf("%d%d%d",&l,&r,&v);if(l>r)swap(l,r);
            printf("%d\n",mp[query(root[l-1],root[r],1,len,v)]);
        }
    }
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值