题目链接:
题目大意:
给
n
个数,
数据范围:
1≤Li≤Ri≤n1≤ki≤Ri−Li+1
解题思路:
对于这种经典的问题,解法有很多种,这里介绍的是基于线段树的解法,算个板子吧!
如果有一个排好序的数组,给一个
x
,直接二分一下就知道
建立线段树的过程和归并排序类似(如果不了解归并排序,可以看看这篇blog:link),一个节点的数列就是其左右儿子的数列归并之后的结果。这个线段树几乎就是归并排序的再现,所以这样的线段树也叫归并树。
还是来个图,实在点。数列
a[]=1,5,6,2,3,7,8,4
,建出来的树就是这样:
接下来就是查询了。
- 如果要查询的区间和当前节点的区间完全没有交集,则返回
0
个。
- 否则,递归计算左右儿子,求和即可。
建树复杂度
O(nlogn)
,询问里面一个
logn
,访问节点一个
logn
,二分节点数列又一个
logn
。总复杂度
O(nlogn+Qlog3n)
。空间复杂度
O(n)
,有个常数。
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
#include <stack>
#include <vector>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 100010;
int n, m;
int a[MaxN + 5];
vector <int> tree[4 * MaxN + 5];
void Build(int rt, int l, int r) {
if(l == r) {
tree[rt].push_back(a[l]);
return;
}
int mid = (l + r) >> 1;
Build(rt << 1, l, mid);
Build(rt << 1 | 1, mid + 1, r);
//分配(r - l + 1)个元素所需的空间
tree[rt].resize(r - l + 1);
//用STL的merge函数将左右儿子的数列合并
merge(tree[rt << 1].begin(), tree[rt << 1].end(), tree[rt << 1 | 1].begin(), tree[rt << 1 | 1].end(), tree[rt].begin());
}
//计算区间[L, R]中不超过x的个数
int query(int rt, int l, int r, int L, int R, int x) {
if(r < L || l > R) return 0;
else if(L <= l && r <= R) {
//二分当前节点的数列中有多少个不超过x的个数
return upper_bound(tree[rt].begin(), tree[rt].end(), x) - tree[rt].begin() ;
}
else {
//递归左右儿子
int mid = (l + r) >> 1;
int lc = query(rt << 1, l, mid, L, R, x);
int rc = query(rt << 1 | 1, mid + 1, r, L, R, x);
return lc + rc;
}
}
int main()
{
while(scanf("%d %d", &n, &m) != EOF)
{
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
Build(1, 1, n);
sort(a + 1, a + n + 1);
for(int i = 1; i <= m; i++) {
int L, R, k;
//求区间[L, R]中的第k个数
scanf("%d %d %d", &L, &R, &k);
int l = 1, r = n;
int mid = 0, res = 0;
while(l <= r) {
mid = (l + r) >> 1;
int ans = query(1, 1, n, L, R, a[mid]);
if(ans >= k) res = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", a[res]);
}
}
return 0;
}