POJ - 2104 主席树入门

题目链接
You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
That is, given an array a[1…n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: “What would be the k-th number in a[i…j] segment, if this segment was sorted?”
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2…5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n — the size of the array, and m — the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 10 9 by their absolute values — the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it — the k-th number in sorted a[i…j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
Sample Output

5
6
3
Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

题意:

n个数,m次查询,问[l,r]之间第k小的数是多少

B站讲解

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<string>
#include<cmath>
#include<cstring>
#include<set>
#include<queue>
#include<stack>
#include<map>
#define rep(i,a,b) for(int i=a;i<=b;i++)
typedef long long ll;
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int sum[N*20],root[N],ls[N*20],rs[N*20],tot,a[N];//ls记录当前节点号的左孩子编号
vector<int>ve;
inline int getid(int x){
    return lower_bound(ve.begin(),ve.end(),x)-ve.begin()+1;//返回值可能是[1,n]之间的数
}
void update(int x,int &y ,int l,int r,int pos){
    y=++tot;
    if(l==r){
        sum[y]=sum[x]+1;
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid){
        rs[y]=rs[x];//连接到上一个树的右孩子
        update(ls[x],ls[y],l,mid,pos);//建立新的边,每次建边是(logn)
    }
    else{
        ls[y]=ls[x];//同上
        update(rs[x],rs[y],mid+1,r,pos);
    }
    sum[y]=sum[ls[y]]+sum[rs[y]];
}

int query(int x,int y,int l,int r,int k){
    if(l==r) return l;
    int mid=(l+r)/2;
    int res=sum[ls[y]]-sum[ls[x]];
    if(res>=k)
        return query(ls[x],ls[y],l,mid,k);
    else
        return query(rs[x],rs[y],mid+1,r,k-res);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        ve.push_back(a[i]);
    }
    sort(ve.begin(),ve.end());
    ve.erase(unique(ve.begin(),ve.end()),ve.end());
    int numid=ve.size();
    for(int i=1;i<=n;i++){
        int id=getid(a[i]);
        update(root[i-1],root[i],1,n,id);
    }bv
    for(int i=1;i<=m;i++){int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",ve[query(root[l-1],root[r],1,n,k)-1]);//离散话之后要变回来
    }
    return 0;
}


总结:
关于求区间的第K小值的问题,就需要用到主席树了

主席树也称函数式线段树也称可持久化线段树。(其实就是支持查询历史版本,本质上就是多个线段树)

其实就是用了前缀和的思想来实现的,构建这个主席树的时候是从小到大来构建的。
参考:https://blog.csdn.net/williamsun0122/article/details/77871278
在这里插入图片描述
感觉这个图比较好理解

还有一个地方需要注意的是,就是在查询的过程中,如果出现左区间个数少于k的时候为什么还需要去看右区间呢
参考https://blog.csdn.net/pengwill97/article/details/80920143

首先保证这个区间内要有KK 个数,否则查询是无意义的。将然后这两个版本树的左子树节点值域做差,得到sum, 如果差值大于等于K,那说明左子树满足查询条件(即左子树有K个数,那么第K大一定在左子树上),就递归去左子树查询第K大;否则就在右子树上,那么我们就去右子树查询第 K−SumK−Sum 大,为什么是这个数值呢?原因是不要忘记左子树有SumSum 个数,所以要查询K−SumK−Sum,如此递归下去,就可以得到结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值