主席树(可持久化权值线段树)的相关知识

主席树(可持久化权值线段树)的相关知识

相关解释:

主席树的核心类似 g i t 操作,维护此时的树与前一个树的差异即可 主席树的核心类似git操作,维护此时的树与前一个树的差异即可 主席树的核心类似git操作,维护此时的树与前一个树的差异即可
即、每修改一个节点,我们就创建一个新的副本,并在副本的基础上进行修改 即、每修改一个节点,我们就创建一个新的副本,并在副本的基础上进行修改 即、每修改一个节点,我们就创建一个新的副本,并在副本的基础上进行修改

其中 , 每个版本我们都认为他是一颗线段树 其中, 每个版本我们都认为他是一颗线段树 其中,每个版本我们都认为他是一颗线段树
因为版本间只有若干节点的不同 因为版本间只有若干节点的不同 因为版本间只有若干节点的不同
因此其他相同节点是各个版本共用的 因此其他相同节点是各个版本共用的 因此其他相同节点是各个版本共用的

空间大小为:

4 ∗ N + 操作数 ∗ l o g N , 但保险起见一般开 N < < 5 (一般空间都可以满足的) , 并且一定不会少的 4 * N + 操作数 * logN, 但保险起见一般开 N << 5(一般空间都可以满足的), 并且一定不会少的 4N+操作数logN,但保险起见一般开N<<5(一般空间都可以满足的),并且一定不会少的


例题:

第k小数

本题解法

(求区间第k小数)

数值上建立线段树 数值上建立线段树 数值上建立线段树
节点上建立前缀和 c n t 保存区间节点数量 节点上建立前缀和cnt保存区间节点数量 节点上建立前缀和cnt保存区间节点数量
将区间 [ L , R ] 的第 k 小数转化为“在第 r 版本和第 l − 1 版本中二分查找节点数量恰好为 k 的版本” 将区间[L, R]的第k小数转化为“在第r版本和第l-1版本中二分查找节点数量恰好为k的版本” 将区间[L,R]的第k小数转化为在第r版本和第l1版本中二分查找节点数量恰好为k的版本

具体实现:

  • 1. 离散化 : 数值较大,节点数比较小 1.离散化: 数值较大,节点数比较小 1.离散化:数值较大,节点数比较小
  • 2. 在数值上建立线段树 : 遍历区间的每一个元素,每遍历一个我们便创建一个新版本即 r o o t [ i ] 2.在数值上建立线段树: 遍历区间的每一个元素,每遍历一个我们便创建一个新版本即root[i] 2.在数值上建立线段树:遍历区间的每一个元素,每遍历一个我们便创建一个新版本即root[i]
    这样我们则右 r o o t [ 0 − n ] 个版本的线段树 这样我们则右root[0-n]个版本的线段树 这样我们则右root[0n]个版本的线段树
  • 3. 用前缀和记录区间中的点的数量 : 询问 [ L , R ] 时,我们用 r o o t [ R ] 减去 r o o t [ L − 1 ] , 3.用前缀和记录区间中的点的数量: 询问[L, R]时,我们用root[R]减去root[L - 1], 3.用前缀和记录区间中的点的数量:询问[L,R]时,我们用root[R]减去root[L1],
    如此即为 [ L , R ] 之间插入的数,即、第 L − 1 版本到第 R 版本之间改变了什么数 如此 即为[L,R]之间插入的数,即、第L-1版本到第R版本之间改变了什么数 如此即为[L,R]之间插入的数,即、第L1版本到第R版本之间改变了什么数
  • 4. 求区间 [ L , R ] = = 求版本 [ L , R ] 4.求区间[L,R] == 求版本[L,R] 4.求区间[L,R]==求版本[L,R]
  • 5. 在 r o o t [ r ] 的版本和 r o o t [ l − 1 ] 的版本之间二分查找 c n t 5.在root[r]的版本和root[l-1]的版本之间二分查找cnt 5.root[r]的版本和root[l1]的版本之间二分查找cnt

步骤 5 举例为 : 步骤5举例为: 步骤5举例为:
主席树_第k小数的理解.png

代码如下:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010;

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

struct Node
{
    int l, r;
    int cnt;
}tr[N << 5];

int root[N], idx; // root[i] 存每棵树的根节点的指针

// 离散化查找函数
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;
}

// 在[l, r]范围内查找x的位置,在x的位置上 cnt ++
int insert(int p, int l, int r, int x)
{
    int q = ++ idx;
    tr[q] = tr[p]; // 复制上一个版本
    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); // 右子树查找
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
    return q; // 返回此指针(idx)给 root[i]
}

// 在tr[q]与tr[p]的版本的差的新树中,找到第k小的数
// 即求出在[l, r]之间插入的数中的第k小数,
int query(int q, int p, int l, int r, int k)
{
    if(l == r) return r;
    
    // 查找第k小数,k可能 == 左子树的某一个数 或 右子树的某一个数的cnt + 左子树cnt之和
    // 因此先计算左子树的cnt,
    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); 
    // 反之,递归右子树 - 左子树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);
    
    // root[i]指向每个版本的树的根节点的指针
    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);
        // 返回的为离散化的下标,加nums[i]取出真正的值
        printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AC自动寄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值