数据结构: 权值线段树——线段树系列(提供模板)

前言:普通线段树 vs 权值线段树

在基础线段树中,我们处理的是数组下标上的区间操作 ------ 节点代表"位置区间",如 [1, n]

但如果我们的问题是:

"在动态插入/删除数字的过程中,如何快速查询:

  • x 的出现次数?
  • 小于 x 的数有多少个?(即 x 的排名)
  • k 小的数是多少?"

这时,普通线段树就无能为力了,因为我们关心的不是"位置",而是"数值本身"。

权值线段树 应运而生 ------ 它将数值范围 作为线段树的区间,每个叶子代表一个可能的数值 ,节点存储该数值范围内的出现次数(权值)

其三大核心操作:单点修改(插入/删除)、前缀和查询(排名)、第 k 小查询


什么是权值线段树?

核心思想:以"值域"为下标,以"频率"为权值

  • 普通线段树 :下标 = 数组索引(位置),值 = arr[i]
  • 权值线段树 :下标 = 数值本身(值域),值 = 该数值的出现次数

本质上,权值线段树就是对"值域"建立的普通线段树,只不过每个位置的"权值"是频率。

权值线段树支持的核心操作

1. 插入/删除一个数(单点修改)

  • 操作 :将数字 x 的频率 +1(插入)或 -1(删除)
  • 实现 :在线段树的叶子 x 处执行 sum += delta
  • 复杂度 :O(log M),其中 M 是值域大小(如 1e5

2. 查询排名(前缀和查询)

  • 问题 :小于 x 的数有多少个? → 即 x 的排名(从 1 开始)
  • 转换 :求值域区间 [1, x-1] 的频率和
  • 实现 :调用区间查询 Ask(1, 1, x-1)(函数具体实现见下方模板)
  • 排名 = Ask(1, 1, x-1) + 1

例如:x=4Ask(1,1,3)=3 → 排名为 4(因为有 3 个数比它小)


3. 查询第 k 小的数(Kth 查询)

  • 问题 :集合中第 k 小的数是多少?
  • 原理 :利用线段树的二分性质
    • 若左子树的 sum ≥ k,说明第 k 小在左半值域
    • 否则,在右半值域,且找第 k - left.sum
  • 实现 :递归 Kth(p, k)(函数具体实现见下方模板)
  • 复杂度:O(log M)

模板中 Kth(p, k) 正是此操作。


值域离散化:处理大范围数值

权值线段树的空间复杂度取决于值域大小 M 。若数值范围很大(如 1e9),但实际数字数量 n 很小(如 1e5),则需离散化(Coordinate Compression)

  1. 收集所有可能用到的数字(插入值、查询值)
  2. 排序 + 去重,建立映射:原数值 → 排名(1~size)
  3. 排名值域 [1, size] 上建权值线段树

离散化后,值域大小 M = n,空间 O(n)


模板

cpp 复制代码
template <int N>struct Segment_tree{  
	#define ls (p<<1)
	#define rs ((p<<1)|1)
    struct{  
        int l,r;  
        int sum;  
    }tr[4*N];  
    void push_up(int p){  
        tr[p].sum=tr[ls].sum+tr[rs].sum;  
    }  
    void Build(int p,int lo,int ro){  //建树,值域为lo~ro
        tr[p].l=lo,tr[p].r=ro;  
        tr[p].sum=0;  
        if(lo==ro) return;  
        int mid=(lo+ro)>>1;  
        build(ls,lo,mid);  
        build(rs,mid+1,ro);  
        push_up(p);
    }  
    void Fix(int p,int x,int k){   //数x增加/减少k  
        if(tr[p].l==tr[p].r){  
            tr[p].sum+=k;  
            return;  
        }  
        int ndmid=(tr[p].l+tr[p].r)>>1;  
        if(x<=ndmid) Fix(ls,x,k);  
        else Fix(rs,x,k);  
        push_up(p);  
    }  
    int Ask(int p,int lo,int ro){  //查询范围内有多少个数,可查询x的排名:Ask(1,1,x-1)+1  
        if(lo<=tr[p].l && ro>=tr[p].r) return tr[p].sum;  
        int res=0,ndmid=(tr[p].l+tr[p].r)>>1;  
        if(lo<=ndmid) res+=Ask(ls,lo,ro);  
        if(ro>ndmid) res+=Ask(rs,lo,ro);  
        return res;  
    }  
    int Kth(int p,int k){  //查询第k小的数  
        if(tr[p].l==tr[p].r) return tr[p].l;  
        if(k<=tr[ls].sum) return Kth(ls,k);  
        else return Kth(rs,k-tr[ls].sum);    
    }  
};
相关推荐
劈星斩月3 分钟前
监督学习算法 之 决策树
学习·算法·决策树
学计算机的计算基8 分钟前
操作系统内存管理全解:虚拟内存、页表、COW、malloc、OOM一篇搞定
java·笔记·算法
tachibana212 分钟前
hot100 前 K 个高频元素(347)
java·数据结构·算法·leetcode
To_OC8 小时前
LC 131 分割回文串:刚学回溯时,我连怎么切字符串都想不明白
javascript·算法·leetcode
旖-旎9 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
To_OC10 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
delishcomcn10 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹11 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
雪碧聊技术11 小时前
力扣 LCR 091. 粉刷房子 —— 动态规划入门详解
算法·动态规划