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


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

    相关解释:

    主席树的核心类似 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 
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
  • 相关阅读:
    【eolink】一个http访问例子
    2022-08-30 第六小组 瞒春 学习笔记
    学习axure都要经历哪个阶段,如何快速入门
    前端工作总结213-解决vuex刷新数据丢失
    网络入侵检测 Network Intrusion Detection System (NIDS)
    测试和验证有什么区别,怎么划分测试集和验证集
    TensorRT Paser加载onnx 推理使用
    人类的态势感知可分为先验、似然、后验的三部分
    027.Python面向对象_类&方法
    00前言说明-Qt自定义控件大全
  • 原文地址:https://blog.csdn.net/m0_61409183/article/details/126272478