题目大意:给出一个数列,求区间第K小数
分析:划分树。就是基于快排原理的线段树。线段树的每一层都类似于一次快排的结果。
建树时,把小于as[mid]的数分到左边,大于as[mid]的数分到右边,相等的根据情况分到左右两边。同时,用一个sum[d][i]数组记录第d层前i个元素比as[mid]小的个数,以便于之后的查询操作时缩小区间以及k。
查询,代码注释蛮清楚的了。[L, R]表示要查询的区间。
int Query(int d, int l, int r, int L, int R, int k) {
int s, ss; //s表示区间[l, L)有多少个元素比as[mid]小,ss表示区间[L, R]有多少个元素比as[mid]小
int mid = (l+r)>>1;
if(l == r) return tree[d][l];
if(l == L) s = 0; //特判。因为区间是左闭右开的
else s = sum[d][L-1];
ss = sum[d][R]-s;
if(ss >= k) return Query(d+1, l, mid, l+s, l+s+ss-1, k);
else return Query(d+1, mid+1, r, mid+1+L-l-s, mid+1+R-l-s-ss, k-ss); //L-l-s表示这次Query的区间之前有多少个大于as[mid]的数分到了右边
} //R-l-s-ss表示这次Query的区间以及它之前共有多少个大于as[mid]的数分到了右边
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 111111;
int tree[20][maxn]; //每一层类似于一次快排的结果
int sum[20][maxn]; //sum[d][i]表示第d层前i个元素有多少个比as[mid]小,加上等于as[mid]的元素分到左区间的个数
int a[maxn], as[maxn]; //a数组表示原始数组,as数组表示排序后数组
void Build(int d, int l, int r) {
int mid = (l+r)>>1;
int lpos = l, rpos = mid+1; //lpos记录放入下一层的左区间的位置,rpos则是记录右区间
int lsame = mid-l+1; //lsame记录有前半个区间有多少个数与as[mid]相等,初始时假设全都相等
for(int i = l; i <= mid; i++)
if(as[i] < as[mid]) lsame--;
for(int i = l; i <= r; i++) {
if(i == l) sum[d][i] = 0;
else sum[d][i] = sum[d][i-1];
if(tree[d][i] == as[mid]) {
if(lsame) { //lsame>0说明与as[mid]相同的元素还可以分到左区间
lsame--;
sum[d][i]++;
tree[d+1][lpos++] = tree[d][i];
}
else tree[d+1][rpos++] = tree[d][i];
}
else if(tree[d][i] < as[mid]) {
sum[d][i]++;
tree[d+1][lpos++] = tree[d][i];
}
else tree[d+1][rpos++] = tree[d][i];
}
if(l != r) {
Build(d+1, l, mid);
Build(d+1, mid+1, r);
}
}
int Query(int d, int l, int r, int L, int R, int k) {
int s, ss; //s表示区间[l, L)有多少个元素比as[mid]小,ss表示区间[L, R]有多少个元素比as[mid]小
int mid = (l+r)>>1;
if(l == r) return tree[d][l];
if(l == L) s = 0; //特判。因为区间是左闭右开的
else s = sum[d][L-1];
ss = sum[d][R]-s;
if(ss >= k) return Query(d+1, l, mid, l+s, l+s+ss-1, k);
else return Query(d+1, mid+1, r, mid+1+L-l-s, mid+1+R-l-s-ss, k-ss); //L-l-s表示这次Query的区间之前有多少个大于as[mid]的数分到了右边
} //R-l-s-ss表示这次Query的区间以及它之前共有多少个大于as[mid]的数分到了右边
int main() {
int T, n, m;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
tree[0][i] = as[i] = a[i];
}
sort(as+1, as+n+1);
Build(0, 1, n);
while(m--) {
int L, R, k;
scanf("%d%d%d", &L, &R, &k);
printf("%d\n", Query(0, 1, n, L, R, k));
}
}
return 0;
}