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

点我看题

题意:给出一个序列,然后求其某个区间内的第k大值。

分析:主席树的模板题,这题不涉及到修改操作,只有查询。

先来看看主席树是什么。

主席树,也就是可持久化的线段树,又称函数式线段树。(看到知乎上对主席树和可持久化线段树有一些区分

那么,可持久化是什么意思呢,目前认为就是去利用前面已经知道的一些数据来减少当前对空间的消耗,通俗的讲,就是我之前已经知道那些值是什么了,那么我现在还要想知道那些值,就直接指向前面就好,不用特意记下来。

主席树的结构就是n+1棵线段树,第i棵线段树记录前i个数的情况。

那么对于这个题来说,我们也可以用主席树求解。

参考代码:

/*主席树*/
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>

using namespace std;
const int maxn = 1e5+10;
int n,q,m;
int a[maxn],hs[maxn];//a为原序列,hs为对a进行hash后的结果
//主席树
int tot;//总结点数
int pt[maxn<<5],lson[maxn<<5],rson[maxn<<5],c[maxn<<5];//结点值,左右儿子,结点对应区间内存在的数的个数

//对原序列做一个哈希,哈希函数为f(x) = x;0
//m为哈希后的序列长度
void InitHash()
{
    for( int i = 1; i <= n; i++)
        hs[i] = a[i];
    sort(hs+1,hs+1+n);
    m = unique(hs+1,hs+1+n)-hs-1;//去重
}

//建树,这里建的是一颗“空树”
int Build( int l, int r)
{
    int rt = tot++;//根节点序号
    c[rt] = 0;//结点代表区间含有的数的个数
    if( l != r)//非叶子结点
    {
        int mid = (l+r)>>1;
        lson[rt] = Build(l,mid);//建左右子树
        rson[rt] = Build(mid+1,r);
    }
    return rt;//返回记录下根节点的值
}

//哈希查找,在hs数组中寻找x的位置
int Hash( int x)
{
    return lower_bound(hs+1,hs+1+m,x)-hs;
}

//更新操作,本题中每一次更新都像是新建一棵树
int Update( int rt, int pos, int val)
{
    //rt为上一棵树的根节点
    int newrt = tot++;;//根节点
    int tmp = newrt;//,临时记录下
    c[newrt] = c[rt]+val;//该结点对应区间的值多了val个,更新个数
    int l = 1, r = m;
    while( l < r)
    {
        int mid = (l+r)>>1;
        if( pos <= mid)//更新的为值在左子树
        {
            lson[newrt] = tot++;//新建左子树
            rson[newrt] = rson[rt];//右子树就直接指向前面已知的相同区间(可持久化)
            newrt = lson[newrt];
            rt = lson[rt];//rt指向自己的左子树
            r = mid;
        }
        else//更新的为右子树
        {
            rson[newrt] = tot++;//同理,新建右子树
            lson[newrt] = lson[rt];//左子树指向已存在的
            newrt = rson[newrt];//
            rt = rson[rt];
            l = mid+1;
        }
        c[newrt] = c[rt]+val;//更新对应个数
    }
    return tmp;//返回答案
}

//总的大左右区间
int Query( int lrt, int rrt, int k)
{
    int l = 1, r = m;
    while( l < r)
    {
        int mid = (l+r)>>1;
        if( c[lson[lrt]]-c[lson[rrt]] >= k)//大于k的话肯定在左边
        {
            r = mid;
            lrt = lson[lrt];
            rrt = lson[rrt];
        }
        else//反之,在右边
        {
            l = mid+1;
            k -= c[lson[lrt]]-c[lson[rrt]];
            lrt = rson[lrt];
            rrt = rson[rrt];
        }
    }
    return l;
}

int main()
{
    while( ~scanf("%d%d",&n,&q))
    {
        for( int i = 1; i <= n; i++)
            scanf("%d",&a[i]);
        //InitHash
        InitHash();
        tot = 0;
        pt[n+1] = Build(1,m);
        for( int i = n; i ; i--)
        {
            int pos = Hash(a[i]);
            pt[i] = Update(pt[i+1],pos,1);
        }
        while( q--)
        {
            int l,r,k;
            scanf("%d%d%d",&l,&r,&k);
            printf("%d\n",hs[Query(pt[l],pt[r+1],k)]);
        }
    }

    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值