题目描述 Description
要求你维护一个数据结构,能够返回区间[l,r]之间第k小的值
输入描述 Input Description
第一行两个数n,m,分别表示数组大小和询问组数
第二行n个不同的整数
a1..an
a
1
.
.
a
n
,表示数组的第i位是
接下来m行每行三个正整数l,r,k,表示询问[l,r]区间中第k小的值
输出描述 Output Description
共m行,每行一个正整数表示询问的答案
样例输入 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
数据范围及提示 Data Size & Hint
1<=n<=100000,1<=m<=5000,ai<=109
1
<=
n
<=
100000
,
1
<=
m
<=
5000
,
a
i
<=
10
9
Solution
这是一道可持久化线段树(主席树)的模板题
主席树的本质是保存几个形态完全相同的历史版本
那么每加入数组的一位都建出一个船新的版本
这样的话第i个版本就保存了前i个数的所有信息
所以说我们需要查询[l,r]的信息只需要将第r个版本-第l-1个版本就好了
那么每棵线段树维护什么信息呢?
因为要求区间第k小,并且已经能将[l,r]中的点信息提取出来,考虑按大小排序后搞个前缀和查询即可
那么线段树的叶子节点保存这个叶子节点所对应的数的出现次数,非叶子节点保存左右节点和即可
每次查询当前节点的左儿子大小和k的大小关系即可
详细代码如下:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int N=200010;
const int INF=0x3f3f3f3f;
int num[N],sorted[N],root[N];
int cnt;
struct T {
int sum,l,r;
T(){}
T(int _sum,int _l,int _r){sum=_sum;l=_l;r=_r;};
}t[N<<5];
void insert(int &root,int pos,int l,int r) {
t[++cnt]=T(t[root].sum+1,t[root].l,t[root].r);
root=cnt;
if(l==r) return;
int m=(l+r)>>1;
if(pos<=m)
insert(t[root].l,pos,l,m);
else
insert(t[root].r,pos,m+1,r);
}
int query(int S,int E,int l,int r,int k) {
if(l==r) return l;
int m=(l+r)>>1;
int sum=t[t[E].l].sum-t[t[S].l].sum;
if(k<=sum)
return query(t[S].l,t[E].l,l,m,k);
else
return query(t[S].r,t[E].r,m+1,r,k-sum);
}
int main() {
int n,m,NUM,pos,T;
while(~scanf("%d %d",&n,&m)) {
cnt=0;root[0]=0;
for(int i=1;i<=n;i++) {
scanf("%d",&num[i]);
sorted[i]=num[i];
}
sort(sorted+1,sorted+n+1);
NUM=unique(sorted+1,sorted+1+n)-(sorted+1);
for(int i=1;i<=n;i++) {
root[i]=root[i-1];
pos=lower_bound(sorted+1,sorted+NUM+1,num[i])-sorted;
insert(root[i],pos,1,NUM);
}
int l,r,k;
while(m--) {
scanf("%d%d%d",&l,&r,&k);
pos=query(root[l-1],root[r],1,NUM,k);
printf("%d\n",sorted[pos]);
}
}
return 0;
}