题意
- 给我们一个长度为 n 的数组 a [],每次询问区间 [l, r] 中的第 k 小数字是几。
思路
- 主席树经典应用,这一题我们可以对 a 建立权值线段树,每个线段树中每个区间维护的是在这个区间中已经出现的数字的数量。
- 每插入第 i 个数字我们就建立一个编号为 i 的新的历史版本,这样我们对于一次,一次查询操作我们就在 l~r 之间的历史版本进行二分操作,但是 l~ r 直接的历史版本是受 1~l-1 之间的历史版本所影响的,为了消除影响我们可以同时二分第 l-1 个历史版本和第 md 的个历史版本(md>=l && md <=r),并在 query 操作查询 query (l-1) 和 query (md) 的时候将两者做差:query (md)-query (l-1) 以消除前 l-1 个数的影响。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N];
vector<int> nums;
struct Tree
{
int l, r, cnt;
} tr[N * 4 + 17 * N];
int root[N], idx;
int find(int x)
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
int build(int l, int r)
{
int p = ++ idx;
if (l == r) return p;
int mid = (l + r) >> 1;
tr[p].l = build(l, mid);
tr[p].r = build(mid + 1, r);
return p;
}
int insert(int p, int l, int r, int x)
{
int q = ++ idx;
tr[q] = tr[p];
if (l == r)
{
tr[q].cnt ++;
return q;
}
int mid = (l + r) >> 1;
if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
else tr[q].r = insert(tr[p].r, mid + 1, r, x);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
int query(int q, int p, int l, int r, int k)
{
if (l == r) return r;
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
int mid = (l + r) >> 1;
if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
nums.push_back(a[i]);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
root[0] = build(0, nums.size() - 1);
for (int i = 1; i <= n; i ++)
root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
int l, r, k;
while (m --)
{
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);
}
return 0;
}