HDU Kth number 主席树

在这里插入图片描述题目就一句话,找到一个区间里第k小的数,详细看代码

//主席树模板题
//求区间第 k 小的树
#include<stdio.h>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=1e5+6;
int n,m,cnt,root[maxn],a[maxn],x,y,k;
//cnt是root的计数,a存放原来的数据。
struct node{
    int l,r,sum;
}hjt[maxn*20];
vector<int>v;//用一个容器来离散化操作
int getid(int x){
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;//+1
}//我们只关心数的相对大小,不关心具体是多少,所以离散化。
//离散化时使用,v是排序去重后的,得到第一个大于或等于x的位置,就是这个数在所有数里的相对大小。

void insert(int l,int r,int pre,int &now,int p){//pre是前一个,now是现在的,插入一个树,就建立一个新版本的主席树,最初没有数据的位置相当于空
//主要思路是复制旧版,修改需要的位置。
    hjt[++cnt]=hjt[pre];//复制旧版
    now=cnt;//now的值是hjt的序号
    hjt[now].sum++;//插入了一个数,总个数+1
    if(l==r)//到了叶子节点,直接返回
        {return;}
    int m=(l+r)>>1;   
    //因为维护的是区间数的个数,所以插入数p 就是插在位置p。
    if(p<=m) {insert(l,m,hjt[pre].l,hjt[now].l,p);}
    else {insert(m+1,r,hjt[pre].r,hjt[now].r,p);}

}
int query(int l,int r,int L,int R,int k){
    if(l==r) {return l;}
    int m=(l+r)>>1;
    int tmp=hjt[hjt[R].l].sum-hjt[hjt[L].l].sum;
    //R版本左孩子的sum减掉L版本左孩子的sum,为所查询的左孩子中数的个数,
    //如果比k小,就在右孩子里查询第k-tmp个数,如果比k大,就在左孩子里查询第k个

    if(k<=tmp) {return query(l,m,hjt[L].l,hjt[R].l,k);}
    else {return query(m+1,r,hjt[L].r,hjt[R].r,k-tmp);}

}

int main(){
    int cas;
    scanf("%d",&cas);
    while (cas--)
    {
        cnt=0;
        v.clear();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            v.push_back(a[i]);
        }//把a[i]放在容器里
        sort(v.begin(),v.end());//排序
        v.erase(unique(v.begin(),v.end()),v.end());//去重

        for(int i=1;i<=n;i++)
            insert(1,n,root[i-1],root[i],getid(a[i]));//建立主席树
        while(m--)
        {
            scanf("%d%d%d",&x,&y,&k);
            printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);
            //每次插入一个就有一个版本的主席树,所以用 y版本的 减掉 x-1版本的,就是所查询区间的所有数组成的树。
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值