POJ-2104:K-th Number(主席树)


题目链接:点击打开链接


题目大意:

给你一串数字,然后查询 l r  区间上第k小的数。


解题思路:

这个就是主席树的模板题了,其实主席树只算辅助作用。本身这道题的解题方法也比较巧妙。

题目会给你一串数,然后求 l r 区间上的第k小的数,

求第k小,这里需要用到一个前缀和的性质,前缀和统计的是区间上出现的数的个数,描述不清楚,举个例子

对于 1,1,2,4,6,7

那么1位置出现2次 2位置出现1次 1~2区间一共就出现了3次 1~5 出现4次 5~10 区间出现2次,那么这个东西有什么用呢,假设我们现在要求这些数中的第3小,那么判断过程就是每次在当前结点判断是否左儿子中数的数量大于当前要求的k 大于的话就往左儿子继续找,否则的话往右儿子找第 k-cnt小即可  cnt为左儿子中数出现的次数,最后递归到最后就可以得到答案了,那么我们如何求任意 l r 区间之间的第k小呢,  假设当前有一个数组 a[N]   a[k]表示前k个数的在整个区间出现的次数, 那么我们如果求 l r 区间上的第k小时 直接执行 a[r] - a[l-1],然后对得到的新的树按以上的算法求解即可。

那么按照正常的方法,我们应该怎么办呢,对每一个a[i]建立一颗线段树,那空间复杂度就要炸了,那么我们应该怎么办呢,其实对于你新建的线段树来说,其实每次修改操作你只改了一个结点,那么新的线段树跟上一棵线段树比起来也就每层改变了一个结点,其他的结点可以用上一棵线段树的结点 大大的减少了空间复杂度。文字难以描述,上图,





通过这样的建树大大节省了空间要求 也就可以进行上述的操作了,以下贴代码,


#include <cstdio>
#include <vector>
#include <algorithm>
#define rank ra
#define pb push_back
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int N=100005;
int n,m,cnt,root[N],a[N],x,y,k;
vector<int> v;
int getid(int x){ return lower_bound(v.begin(),v.end(),x)-v.begin()+1; }
struct node
{
    int l,r,sum;
}T[N*40];
void update(int l,int r,int &x,int y,int pos)   //建立新的线段树
{
    T[++cnt]=T[y];
    T[cnt].sum++;
    x=cnt;
    if(l==r)
        return ;
    int mid=(l+r)>>1;
    if(mid>=pos)    //只新建需要更新的结点即可
        update(l,mid,T[x].l,T[y].l,pos);
    else
        update(mid+1,r,T[x].r,T[y].r,pos);
}
int query(int l,int r,int x,int y,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)>>1;
    int sum=T[T[y].l].sum-T[T[x].l].sum;    //查询操作
    if(sum>=k)
        return query(l,mid,T[x].l,T[y].l,k);
    else
        return query(mid+1,r,T[x].r,T[y].r,k-sum);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        v.pb(a[i]);
    }
    sort(v.begin(),v.end());    //排序去重
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++)
        update(1,n,root[i],root[i-1],getid(a[i]));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值