主席树 不处理区间修改

存下数据结构的所有历史版本 核心思想是只记录每个版本与前一个版本不同的节点
凡是有变化的点就裂开 否则不用动

第k子树#

因为线段树的性质,所以每个点的左子树的值域区间 <=右子树的值域区间。
所以我们先看左子树区间有多少个数,记为cntleft。

可以看出BSTrBSTr跟BSTl−1BSTl−1的区别就是多插入了中间那第ll到第rr个数, 一共r−l+1r−l+1个元素
容易看出, 如果我们用BSTr的左子树上面的元素个数减去BSTl−1的左子树元素个数, 结果就会是这r−l+1个新元素里面插入到左子树上的元素个数(设为x), 显然, 这x个元素是肯定比这r−l+1个新元素里面插到右子树上的元素小的.

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 100010, M = 10010;

int n, m;
int a[N];
vector<int> nums;

struct Node
{
    int l, r;
    int cnt;
}tr[N * 4 + N * 17];

int root[N], idx;

int find(int x)
{
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

int build(int l, int r)//l 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)// 原来的根节点 做儿子 有儿子 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;//更新下cnt 为左右儿子的cnt之和 pushhup
    return q;
}

int query(int q, int p, int l, int r, int k)//
{
    if (l == r) return r;//叶子节点就是答案
// tr[tr[q].l].cnt - tr[tr[p].l].cnt的结果是求出在p之后插入到q这些数之后,
有多少个数(cnt)插入了p的左子树, 由于p的内容肯定不能出现在l r之间(p根节点就是root[l-1]), 
    // 所以cnt就是相当于"存在q左子树里面但不存在于1, l 之间的数的个数" 
   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);//什么都没有只是先搭建好骨架

      // 左右参数给0和nums.size()-1是因为离散化之后的值域就是在0, nums.size()-1之间
    // 要插入必须得把这些地方全包才能保证找得到插入点
    for (int i = 1; i <= n; i ++ )
        root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
                         //前一个版本   左右边界0 - size-1  对应的离散值
    while (m -- )
    {
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);// 左右边界
                              
    }

    return 0;
}



posted @   liang302  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示
主题色彩