• 线段树应用——权值线段树


    权值线段树的定义

     权值线段树是一种建立在基本线段树之上的数据结构。因此它的基本原理仍是基于对区间的维护操作。但与线段树相区分的点是:权值线段树维护的信息与基本线段树有所差异:

     1.基本线段树中每个结点可以用来维护一段区间的最大值或总和等;
     2.权值线段树每个结点维护的是一个区间的数出现的次数。

     实际上,权值线段树可以看作是一个桶,桶的操作它都支持,并且能够以更快的方式去完成。

     根据权值线段树的性质,我们可以知道其主要用途:

     权值线段树一般用于维护一段区间的数出现的次数,从它的定义来看,它可以快速计算出一段区间的数的出现次数。

     在实际应用中,我们使用权值线段树查询区间第 K K K大的值。

    权值线段树的原理与操作

     假设我们现在有一个序列 A = 1 , 2 , 2 , 3 , 3 , 3 , 4 , 4 , 4 , 4 A={1,2,2,3,3,3,4,4,4,4} A=1,2,2,3,3,3,4,4,4,4我们先来通过三张图片感性理解一下这两种线段树的不同。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    上图相关解释:
      A [ 1 ] = 1 A[1]=1 A[1]=1:原序列中存在值为 1 1 1的元素 1 1 1个
      A [ 2 ] = 1 A[2]=1 A[2]=1:原序列中存在值为 2 2 2的元素 2 2 2个
      A [ 3 ] = 1 A[3]=1 A[3]=1:原序列中存在值为 3 3 3的元素 3 3 3个
      A [ 4 ] = 1 A[4]=1 A[4]=1:原序列中存在值为 4 4 4的元素 4 4 4个

      D [ k ] D[k] D[k]:一定区间内所含元素的个数。

     可以看出,基础线段树和权值线段树的一个较大的区别点在于:(此处特指使用数组储存树结构)基础线段树根据初始的一个序列建立树结构,具有单独的建树操作;而权值线段树不需要进行建树操作,初始状态下默认建立一棵所有点权为 0 0 0的空树,对于每个元素,在遍历的时候相应的结点做自增运算。

    查找第K大元素的方法:

     由于权值线段树每个节点记录了某段区间内包含的元素个数,且元素在叶子节点构成的序列中是有序的:

     对于整列数中第 K K K大/小的值,我们从根节点开始判断(这里按第 K K K大为例),如果 K K K比右儿子大,就说明第 K K K大的数在左儿子所表示的值域中。然后, K K K要减去右儿子。(这是因为继续递归下去的时候,正确答案,整个区间的第 K K K大已经不再是左儿子表示区间的第 K K K大了)。如此递归到叶子节点时即为答案。(其实跟BST的原理差不多)。

     整棵线段树的根节点就表示整个值域有几个数。

     我们很容易看出:在权值线段树中,采用元素到下标数组映射的方式进行插入。这样会导致一个问题:在数据离散程度较大的情况下,空间的利用效率比较低。因此我们对于线段树所开空间不足时,常采用离散化的方式进行解决。

    查询第 K K K大/小元素

    #define ls p<<1
    #define rs p<<1|1 
    
    int query(int p,int l,int r,int k){//第k大 
        if(l==r)return l;//到达根节点,找到了 
        int mid=(l+r)/2,right=t[rs];//这里的t数组存储的是区间有多少个数 
        if(k<=right)return query(rs,mid+1,r,k);//就在右子树中找 
        else return query(ls,l,mid,k-right);//在左子树中找 
    }
    
    int query(int p,int l,int r,int k){//第k小 
        if(l==r)return l;
        int mid=(l+r)/2,left=t[ls];
        if(k<=left)return query(ls,l,mid,k);
        else return query(rs,mid+1,r,k-left);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    单点更新

    #define ls p<<1
    #define rs p<<1|1
    
    void update(int p,int l,int r,int x){
        if (l==r)t[p]++;
        else{
            int mid=(l+r)/2;
            if(x<=mid)update(ls,l,mid,x);
            else update(rs,mid+1,r,x);
            t[p]=t[ls]+t[rs];
        }
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    查询某个数出现的个数(二分实现)

    #define ls p<<1
    #define rs p<<1|1
    
    int find(int p,int l,int r,int x){
        if (l==r)return t[v];
        else{
            int mid=(l+r)/2;
            if (x<=mid)return find(ls,l,mid,x);
            else return find(rs,mid+1,r,x);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    查询一段区间的数出现的次数

    #define ls p<<1
    #define rs p<<1|1
    
    int find(int p,int l,int r,int x,int y){
        if (x<=l and r<=y) return tree[v];
        int mid=(l+r)/2,ans=0;
        if(x<=mid)ans+=find(ls,l,mid,x,y);
        if(y>mid)ans+=find(rs,mid+1,r,x,y);
        return ans;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    离散化就不用我讲了吧

  • 相关阅读:
    如何用selenium库自动登录校园网网址?
    【刷题之路 | Java & Python】两数之和(暴力枚举&哈希表)
    第5次作业练习题(第五章:总线与接口、中断、DMA)
    Spring Boot 4.0:构建云原生Java应用的前沿工具
    TDengine+OpenVINO+AIxBoard,助力时序数据分类
    2024年第十五届蓝桥杯研究生组题目总结(Java、Python、C++)
    丢失d3dx11_43.dll如何修复,d3dx11_43.dll丢失的5种解决方法
    区块链游戏的开发流程
    斯坦福密码学 —— 02流密码
    在安卓项目中使用 FFmpeg 实现 GIF 拼接(可扩展为实现视频会议多人同屏效果)
  • 原文地址:https://blog.csdn.net/Lucas_FC_/article/details/126178715