归并树与线段树的区别是,线段树的结点维护一个值,而归并树的结点维护一个有序数组。每个节点的数列是其两个儿子节点的数列合并后的结果。这颗线段树正是归并排序的完整再现。
要计算在某个区间上不大于x的数的个数,只需:
- 如果所给区间与当前区间没有交集,返回0
- 如果所给区间完全包含当前区间,使用二分搜索找出当前区间的不大于x的数的下标再加1,返回这个值
- 否则对两个儿子递归地进行计算之后返回他们的和。
由于每个深度只访问常数个结点,因此找出某区间内不超过x的数的个数的时间复杂度是O(log2n)
归并树找区间内第k大时间复杂度:
建树:O(nlogn)
二分法找x:O(logn)
求不超过x的数的个数:O(log2 n)
整个算法的时间复杂度:O(nlogn+mlog3n).m为询问个数
public class Main{
static int maxn=100100,n,arr[]=new int[8*maxn];//注意如果扩n的话开4*maxn会不够用
static ArrayList<Integer> dat[]=new ArrayList[8*maxn];
static int upper_bound(ArrayList<Integer> a,int x) {
int l=-1;
int r=a.size();
while(r-l>1) {
int mid=(r+l)/2;
if(a.get(mid)<=x) {
l=mid;
}else {
r=mid;
}
}
return l;
}
static void merge(ArrayList<Integer> arr1,ArrayList<Integer> arr2,ArrayList<Integer> target){//将有序数列arr1、arr2合并为有序的target.
int i=0;
int j=0;
while(i<arr1.size()&&j<arr2.size()) {
if(arr1.get(i)<arr2.get(j)) {
target.add(arr1.get(i++));
}else {
target.add(arr2.get(j++));
}
}
while(i<arr1.size()) {
target.add(arr1.get(i++));
}
while(j<arr2.size()) {
target.add(arr2.get(j++));
}
}
static void init(int _n) {//将n扩大到2的幂。
n=1;
while(n<_n) {
n*=2;
}
static void init(int rt,int l,int r) {//建树。
dat[rt]=new ArrayList<Integer>();//每次用到的时候再初始化,如果一开始把8*maxn个全初始化会超时!
if(r-1==l) {
dat[rt].add(arr[l]);
}else {
int mid=(l+r)/2;
init(rt*2+1,l,mid);
init(rt*2+2,mid,r);
merge(dat[rt*2+1],dat[rt*2+2],dat[rt]);
}
}
static int query(int i,int j,int x,int l,int r,int rt) {//查询原数列区间[i,j)内不大于x的数的个数
if(r<=i||l>=j) {
return 0;
}else if(l>=i&&r<=j) {//完全包含
return upper_bound(dat[rt],x)+1;
}else {
int lc=query(i,j,x,l,(l+r)/2,2*rt+1);
int rc=query(i,j,x,(l+r)/2,r,2*rt+2);
return lc+rc;
}
}
public static void main(String args[]) throws IOException {
PrintWriter out=new PrintWriter(System.out);
InputReader sc=new InputReader(System.in);
int nums[]=new int[maxn];
n=sc.nextInt();
int N=n;
int m=sc.nextInt();
for(int i=0;i<n;i++) {
arr[i]=sc.nextInt();
nums[i]=arr[i];
}
Arrays.sort(nums,0,n);
init(n);
init(0,0,n);
for(int i=0;i<m;i++) {
int I=sc.nextInt()-1;
int J=sc.nextInt();
int X=sc.nextInt();
int l=-1;
int r=N;
while(r-l>1) {//在原数列的排序数列nums[]中寻找到满足在[i,j)范围内有不少于k个小于等于x的x值的lower_bound下标r。
int mid=(l+r)/2;
int c=0;
c=query(I,J,nums[mid],0,n,0);
if(c>=X) {
r=mid;
}else {
l=mid;
}
}
//out.println(r);
out.println(nums[r]);
}
out.flush();
out.close();
}
}