255. 第K小数 - AcWing题库 给定长度为 的整数序列 ,下标为

现在要执行 次操作,其中第 次操作为给出三个整数 ,求 (即 的下标区间 )中第 小的数是多少。

输入格式

第一行包含两个整数

第二行包含 个整数,表示整数序列

接下来 行,每行包含三个整数 ,用以描述第 次操作。

输出格式

对于每次操作输出一个结果,表示在该次操作中,第 小的数的数值。

每个结果占一行。

数据范围

输入样例:

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

输出样例:

5
6
3

思路

区间第K小数是一个静态问题, 查询不会修改原本的内容, 有很多种解法:

  • 归并树, 时间, 不支持修改, 且实现很麻烦
  • 划分树, 时间, 空间 , 不支持修改
  • 线段树套平衡树, 时间 , 空间 , 支持修改
  • 可持久化线段树, 时间, 空间, 不支持修改

可持久化线段树—主席树也可以套平衡树。

该题建立线段树时需要根据数值建立, 即 代表 的个数。再看数据范围, 每个数值的范围很大, 但总个数只有1e5个, 可以进行离散化处理。

先不考虑区间, 怎么求整体的第k小数?可以通过二分优化方法查找: image.png 中点数量 , 那么第K小数一定不在 中, 直接搜左边区间。 同理, 若 , 则答案一定在右区间。

接着再考虑 的情况, 跟 Tire 树 可持久化 类似, 从第一个数加到第R个数时, 查询当前的版本 就能得到 的第K小数。

至于 区间, 可以找出 的个数 , 跟 的个数 , 根据个数的性质, 让 就是区间 的个数。因此在二分时搜索一个满足条件 的最小的 即可。

线段树节点开的个数就是 加上因为修改新加的节点数

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
using namespace std;
const int N = 1e5 + 10, M = 1e4 + 10;
int a[N];
struct Node {
    int l, r;
    int cnt;
}tr[N * 4 + N * 17];
int root[N], idx;
vector<int> nums;
int n,m;
 
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()
{
    cin >> n >> m;
    for(int i = 1; i <= n;i ++) 
    {
        cin >> 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]));
    
    while(m--)
    {
        int l, r, k;
        cin >> l >> r >> k;
        cout << nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)] << endl;
    }
    
    return 0;
}