题目:poj 2104
大意:给你一个长度为n的数列,每次查询数列下标为[i,j]区间里面升序排序后第k大的数
(我刚刚加入学校集训队暑假acm集训的时候遇到这题直接头铁sort。。。。现在想起来还真是天真)
由此引出归并树(其实这一题用主席树更快,点击传送!)
首先,区间升序排序第k大需要做一个小小的转换,不妨设那个数就是aim,就是区间内比aim小的<k个,小于等于aim的>=k个。(区间中可能不止一个aim),转换下来之后这道题可以转化为数区间内比aim小的数的个数。那么我怎么知道区间内那个aim是什么呢(直接告诉了还做个屁。。),那么就对原来的数组排序,二分查找,利用每个数所查询到的值来判断。
那么归并树的做法是怎么样的呢,借用《挑战程序设计竞赛》的一张图片
大体做法如上图所示(原谅我字丑)
实际上,由于区间内的aim不止一个,很可能有多个
lower_bound 自行体会。。。。
最后放上AC代码:
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=100000*4+5;
vector<int> tree[maxn];
int a[100005];
void build_tree(int l,int r,int index){
if(l==r){
tree[index].push_back(a[l]);
return;
}
int mid=(l+r)>>1, lson=index<<1, rson=index<<1|1;
build_tree(l,mid,lson);
build_tree(mid+1,r,rson);
tree[index].resize(r-l+1); //没这句merge会炸
merge(tree[lson].begin(),tree[lson].end(),tree[rson].begin(),tree[rson].end(),tree[index].begin());
}
int query(int q_l,int q_r,int L,int R,int index,int aim){
if(q_l>R||q_r<L) return 0; //没啥关系的区间返回0
else if(q_l<=L&&q_r>=R) return upper_bound(tree[index].begin(),tree[index].end(),aim)-tree[index].begin();
else{ //有区间没在分好的地方那就继续二分下去
int res=0,mid=(L+R)>>1;
res+=query(q_l,q_r,L,mid,index<<1,aim);
res+=query(q_l,q_r,mid+1,R,index<<1|1,aim);
return res;
}
}
int main(){
int n,q; cin>>n>>q;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build_tree(1,n,1);
sort(a+1,a+1+n);
while(q--){
int l,r,k;
scanf("%d %d %d",&l,&r,&k);
int ll=1,rr=n;
while(ll<rr){ //二分查找那个值到底是多少
int mid=(ll+rr)>>1;
int res=query(l,r,1,n,1,a[mid]);
if(res>=k){
rr=mid;
}
else ll=mid+1;
}
cout<<a[rr]<<endl;
}
return 0;
}