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

前言:普通线段树 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=4,Ask(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);    
    }  
};
相关推荐
傅科摆 _ py9 小时前
DP,贪心,堆和栈,二分力扣复习
开发语言·python·算法
Shaoshing9 小时前
LeetCode100 42.接雨水
java·算法·leetcode
域限奇点访客10 小时前
用AI生成问卷的网站怎么选?看题目录入结构、题型映射和生成后的校验
算法
not coder11 小时前
一文讲通--从树到红黑树
数据结构·算法
octopus_c11 小时前
C++ 入门
开发语言·c++·算法
爱吃香菜的初学者11 小时前
八.数据结构——哈希表(1)
数据结构·散列表
ZhengEnCi11 小时前
2026年10月30日AI早报:Gemini 4 Carbon登顶、DeepSeek融资1055亿、智元机器人上岗实测
人工智能·算法
强哥聊算力11 小时前
《算力江湖·Intel帝国往事》第三集:安迪·格鲁夫的铁血时代:Intel为什么能把对手逼到墙角?
运维·人工智能·科技·算法·ai·gpu算力
Shaoshing11 小时前
LeetCode100 11. 盛最多水的容器
算法·leetcode
骑着蜗牛撵大象32712 小时前
558 万美元训出 671B 模型:DeepSeek-V3 算法与系统的协同拆解
分布式·算法·moe·deepseek-v3·mla·fp8训练