[POJ2104]K-th Number

博客围绕区间k小值查询问题展开,给出题面、输入输出要求等。指出这是主席树经典例题,介绍通过引入权值线段树和前缀和概念求解,虽建立n个线段树不现实,但添加新点仅影响部分节点,查询时利用前缀和思想计算差值,最后给出代码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

K-th Number

题面

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

思路

提取一下,这是一个区间k小值查询问题,也是一道主席树的经典例题。

首先,我们可以引入一个权值线段树的概念,即第k个叶子节点存储的是第k个数的个数(当然,数据要离散化)。那么区间 \([l,r]\) 则代表了排名为l的数到排名为r的数这个区间内共有多少个数。也就是说,权值线段树可以在 \(\log_{2} n\) 的时间内查询到数列k小值。

我们可以在权值线段树的的基础上加入前缀和的概念。我们可以建立n个线段树,第i个线段树包含的数据为 \([1,i]\) 。假设我们需要求区间 \([l,r]\) 的k小值。那么我们可以建立一颗新的线段树,把每个节点用第 \(r\) 棵线段树上这个节点的值减去第 \(l-1\) 棵线段树上这个节点的值。可以理解为第 \(l-1\) 棵线段树上所有点的值是插入[1,l-1]是更改的,为了屏蔽掉l之前的操作,我们用前缀和的思路把l之前操作带来的权值改变滤除即可。

很显然,建立n个线段树在空间上和时间上都是不现实的。另外添加一个新的线段树的操作实质是添加一个新的点。而添加一个点只会对修改路径上的 \(\log_{2} n\) 个点产生影响,那我们只需要新加入被修改的点即可。同时维护一个根节点列表,记录每个版本的根节点即可。

查询的时候,同时从两个版本(即l-1和r)的根同时开始遍历,动态利用前缀和思想计算每个节点在两个节点中的差值。(其与构造一个l-1到r的线段树等效)。然后用与平衡树查询类似方法查询即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define maxn (int)(1e5+1000)
int ls[maxn<<5],rs[maxn<<5],sum[maxn<<5],n,m,root[maxn],idx;
using namespace std;
int a[maxn],sorted[maxn],len;
int getid(int a){
    return lower_bound(sorted+1,sorted+1+len,a)-sorted;
}
int build(int l,int r){
    int now=++idx;
    if(l==r)return now;
    int mid=(l+r)>>1;
    ls[now]=build(l,mid);
    rs[now]=build(mid+1,r);
    return now;
}
int insert(int num,int l,int r,int mark){
    int now=++idx;
    rs[now]=rs[mark];ls[now]=ls[mark];sum[now]=sum[mark]+1;
    if(l==r)return now;
    int mid=(l+r)>>1;
    if(num<=mid){
        ls[now]=insert(num,l,mid,ls[mark]);
    }
    else{
        rs[now]=insert(num,mid+1,r,rs[mark]);
    }
    return now;
}
int query(int ln,int rn,int l,int r,int kth){
    if(l==r)return l;
    int mid=(l+r)>>1;
    int size=sum[ls[rn]]-sum[ls[ln]];
    if(kth<=size){
        return query(ls[ln],ls[rn],l,mid,kth);
    }
    else{
        return query(rs[ln],rs[rn],mid+1,r,kth-size);
    }
    return 0;
}
int main(){
    //freopen("in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){scanf("%d",&a[i]);sorted[i]=a[i];}
    sort(sorted+1,sorted+1+n);
    len=unique(sorted+1,sorted+1+n)-sorted-1;
    root[0]=build(1,len);
    for(int i=1;i<=n;i++){
        root[i]=insert(getid(a[i]),1,len,root[i-1]);
    }
    for(int i=1;i<=m;i++){
        int l,r,k;scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",sorted[query(root[l-1],root[r],1,len,k)]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/GavinZheng/p/10812766.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值