• 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;
    }
  • 相关阅读:
    Springboot系列 1
    从-1起步学习SpringBoot
    WinForm与WPF下跨线程调用控件
    利用FileSystemWatcher实现磁盘文件监控
    HashTable初次体验
    【原创】国网远程加密机认证软件开发2(认证步骤及代码)
    【原创】国网远程加密机认证软件开发1(相关说明)
    第一次做socket的一些心得
    ArchLinux 安装记录
    oh my zsh安装
  • 原文地址:https://www.cnblogs.com/QingyuYYYYY/p/12307874.html
Copyright © 2020-2023  润新知