歸併樹與線段樹的區別是,線段樹的結點維護一個值,而歸併樹的結點維護一個有序數組。每個節點的數列是其兩個兒子節點的數列合併後的結果。這顆線段樹正是歸併排序的完整再現。
要計算在某個區間上不大於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();
}
}