归并树是在建树的过程中保存归并排序。
归并树
以1 5 2 6 3 7为例:
把归并排序递归过程记录下来即是一棵归并树:
[1 2 3 5 6 7]
[1 2 5] [3 6 7]
[1 5] [2] [6 3] [7]
[1][5] [6][3]
用对应的下标区间建线段树:(这里下标区间对应的是原数列)
[1 6]
[1 3] [4 6]
[1 2] [3] [4 5][6]
[1][2] [4][5]
每次查找[l r]区间的第k大数时,在[1 2 3 4 5 6 7]这个有序的序列中二分所要找的数x,然后对应到线段树中去找[l r]中比x小的数有几个,即x的rank。由于线段树中任意区间对应到归并树中是有序的,所以在线段树中的某个区间查找比x小的数的个数也可以用二分在对应的归并树中找。这样一次查询的时间复杂度是log(n)^2。
要注意的是,多个x有相同的rank时,应该取最大的一个。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mid ((t[rt].l+t[rt].r)>>1)
const int maxn = 100500;
struct node {
int l , r;
}t[maxn<<2];
int num[maxn] , mer[20][maxn];
int n , q;
void build(int l,int r,int rt,int deep) {
t[rt].l = l; t[rt].r = r;
if(l == r) {
mer[deep][l] = num[l];
return;
}
build(l,mid,ls,deep+1);
build(mid+1,r,rs,deep+1);
int i=l ,j=mid+1 , p = l;
/**
while(p <= r) {
if(i > mid || mer[deep+1][i] > mer[deep+1][j])
mer[deep][p++] = mer[deep+1][j++];
else
mer[deep][p++] = mer[deep+1][i++];
}
**/
while(i <= mid && j <= r) {
if(mer[deep+1][i] < mer[deep+1][j]) mer[deep][p++] = mer[deep+1][i++];
else mer[deep][p++] = mer[deep+1][j++];
}
while(i <= mid) mer[deep][p++] = mer[deep+1][i++];
while(j <= r) mer[deep][p++] = mer[deep+1][j++];
}
int query(int l,int r,int rt,int deep,int key) {
if(t[rt].r < l || t[rt].l > r) return 0;
if(t[rt].l >= l && t[rt].r <= r)
return lower_bound(&mer[deep][t[rt].l],&mer[deep][t[rt].r]+1,key) - &mer[deep][t[rt].l];
return query(l,r,ls,deep+1,key) + query(l,r,rs,deep+1,key);
}
int solve(int l,int r,int k) {
int ll = 1 , rr = n , mm;
while(ll < rr) {
mm = (ll + rr + 1) >> 1;
int tmp = query(l , r , 1 , 1 , mer[1][mm]) + 1;
if(tmp <= k) ll = mm;
else rr = mm - 1;
}
return mer[1][ll];
}
int main() {
while(~scanf("%d%d",&n,&q)) {
for(int i=1;i<=n;i++) scanf("%d" , &num[i]);
build(1 , n , 1 , 1);
int l , r , k;
while(q--) {
scanf("%d%d%d",&l,&r,&k);
int ans = solve(l , r , k);
printf("%d\n" , ans);
}
}
return 0;
}