解题报告:P3834 模板可持久化线段树 2(主席树)详解

Posted 繁凡さん

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解题报告:P3834 模板可持久化线段树 2(主席树)详解相关的知识,希望对你有一定的参考价值。

P3834 【模板】可持久化线段树 2(主席树)

题解 P3834 【【模板】可持久化线段树 2(主席树)】

1)静态求第k大数

可持久化线段树,不能用堆的方法存子结点了,所以用指针l表示左儿子r表示右儿子

可持久化线段树难以处理区间修改,因为很难处理懒惰标记,除非使用永久化标记线段树

新版本其他的都不变,只把新的点替换,其他的性质例如左右儿子是谁不变,所以每新加入一个数,也就是到了新版本,就完成与一次新老版本的迭代(tr[q] = tr[p] q是新版本p是老版本)

线段树维护的是整个值域。
在数值上建立一个线段树,维护cnt表示每个数值的区间上一共有几个数。

线段树和平衡树都是二叉树,所以我们需要做二分的时候可以考虑能否在树里做二分。

我们首先考虑[1,R]这个区间的个数。很明显我们可以直接用第R个版本即可。
但是我们要求的是[L,R]这个区间,有两个限制。所以我们利用类似前缀和的思想,用第R个版本个数cnt1减去第L-1个版本cnt2
即可得到LR之间的个数:cnt1-cnt2

每次都是个上一个版本比较
l指的是左儿子,不是左边界

由于数据范围达到了1e9
所以需要离散化

//线段树维护的是数值

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

using namespace std;
const int N = 500007, M = 10007, INF = 0x3f3f3f3f;

int n, m;
int idx;
int a[N];
int root[N];
vector<int> nums;
struct Tree
    int l, r;
    int cnt;//表示的是这个值域区间一共有多少个数
tr[N * 4 + N * 17];//log(1e5) ≈ 17

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)//开始把n个数插入(迭代新版本)
    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 mid = l + r >> 1;
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    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);
    //这里注意往右区间找的时候第k大已经变成了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]));

    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;


以上是关于解题报告:P3834 模板可持久化线段树 2(主席树)详解的主要内容,如果未能解决你的问题,请参考以下文章

P3834 模板可持久化线段树 1(主席树)

P3834 模板可持久化线段树 1(主席树)

p3834 可持久化线段树模板(主席树)

P3834 模板可持久化线段树 1(主席树)

P3834 模板可持久化线段树 1(主席树) 整体二分

P3834 模板可持久化线段树 1(主席树)