luogu P3834 【模板】可持久化线段树 1(主席树) 查询区间 [l, r] 内的第 k 小/大值

————————————————
版权声明:本文为CSDN博主「ModestCoder_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ModestCoder_/article/details/90139481

//主席树 
//难以处理区间修改操作,很难处理懒标记 
//l,r代表左右子节点的下标 
//cnt表示当前区间中一共多少个数 

//离散化
//在数值上建立线段树,维护每个数值区间中一共多少个数
//
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 200010; 
int n, m;
int a[N];
vector<int> nums;
struct Node
{
    //左儿子和右儿子的
    //左边都是比l+r>>1小的
    //右边都是大的 
    int l, r;
    //区间里一共多少个数 
    int cnt;
}tr[N << 5];
//根节点    树里可用节点的下标 
int root[N << 5], 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;
}
//原来的根节点,左右边界,        插入的位置(原来的数字离散化后的排名) 
//                 0,nums.size()-1,
int insert(int p, int l, int r, int x)
{         
    //从0号点开始查找
    //root的cnt表示的是1到num.size()-1之间数字的个数
    //所以root这个点肯定会变化
    //所以要开新的点 
    int q = ++ idx;    
    //在没有找到之前 
    //先赋值过来,上一个版本的根节点 
    //
    tr[q] = tr[p];
    //如果是叶节点,找到要更新的店 
    //也就是找到x这个位置了 
    //就是去找要找插入的点 
    if (l == r)
    {
        //新点cnt++ 
        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);
    //左右儿子的cnt的和 
    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; 
    //左区间表示
    //1到 tr[q].l中数字的个数- 1到tr[p].l中数字的个数
    //这些数字都是属于         要        查询的区间中的数字 
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    //中点,左右儿子的中点 
    int mid = l + r >> 1;
    //如果k<=cnt
    //也就是要查询的区间(下标)中的数字插入到左半边的数字大于等于k
    //那么答案对应的离散化之后的坐标就一定在左边 
    if (k <= cnt)
        return query(tr[q].l, tr[p].l, l, mid, k);
    //或者都在右边,这里k要更新为k-cnt,要除去左半边的 
    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] 
    root[0] = build(0, nums.size() - 1);
    for (int i = 1; i <= n; i ++ )
        //第i个版本的线段树
        //是和i-1版本的线段树比较,左右边界是0到 nums.size() - 1,在find(a[i])这个位置加1
        //                                                            对应a[i]的离散值 
        root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
         
    while (m -- )
    {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        //要返回离散化前的值 
        printf("%d
", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/QingyuYYYYY/p/12307874.html