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

前言:普通线段树 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);    
    }  
};
相关推荐
先吃饱再说4 小时前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰7 小时前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术8 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六12 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术12 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize13 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考1 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
CSharp精选营1 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法