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
",sorted[query(root[l-1],root[r],1,len,k)]);
}
return 0;
}