news 2026/5/30 9:47:30

信奥赛C++提高组csp-s之平衡树(Treap)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
信奥赛C++提高组csp-s之平衡树(Treap)

信奥赛C++提高组csp-s之平衡树(Treap)

平衡树概述

为什么需要平衡树?

二叉查找树(Binary Search Tree, BST)的查找、插入、删除操作时间复杂度为 O(h),其中 h 为树的高度。在理想情况下,BST 的高度为 O(log n),但在最坏情况下(例如插入的节点序列本身有序),BST 会退化成单链表,性能下降到 O(n)。

平衡树的核心思想是在进行插入和删除操作时,通过旋转或重构等方式保持树的高度始终维持在 O(log n) 量级,从而保证所有操作的时间复杂度稳定在 O(log n)。

常见平衡树类型

常见的平衡树包括:

类型特点适用场景
Treap(树堆)利用随机优先级维持堆性质,简单易写权值平衡树(P3369)、可持久化(P3835)
Splay通过伸展操作将访问节点旋至根,常数较大但功能强大区间操作(P3391)、序列维护(P2596)
替罪羊树通过暴力重构维持平衡,无需旋转代码简单、避免复杂旋转逻辑
SBT通过维护子树大小实现平衡动态顺序统计

其中,Treap是CSP-S考场中最常用的平衡树。本文将以 Treap 为核心进行讲解。

Treap核心知识点详解

Treap(树堆)是一种结合了二叉搜索树(BST)和堆特性的平衡树结构。它通过为每个节点赋予一个随机的优先级(pri),并利用旋转操作来同时维护BST的有序性和堆的优先级,从而实现了期望的平衡复杂度,非常适合CSP-S竞赛中快速编写和调试。

1. 数据结构定义
// 用数组模拟静态树,速度更快intv[N];// 节点的值 (key)intp[N];// 随机优先级 (priority)intl[N];// 左儿子intr[N];// 右儿子intsz[N];// 子树大小intct[N];// 当前值的重复个数
  • 存储方式:采用数组模拟而非结构体或指针,这能大幅提升运行效率。
  • 逻辑结构:每个节点存储v值来维护BST性质,同时存储一个随机生成的p值来维护大根堆性质。
  • 子树大小sz:用于高效地查询排名和第k大的数。当节点值重复时,我们将其计数ct累加,而不是插入多个相同节点,这能显著简化删除操作并节省空间。
2. 核心操作原理
操作主要代码原理说明
旋转void rot(int &u, int d)在不破坏BST中序遍历顺序的前提下,通过左旋/右旋改变父子关系。例如,右旋会将左子节点提升为父节点,以维护p值的堆性质。
插入void ins(int &u, int x)1. 若节点为空,创建新节点。
2. 若x等于当前v[u],增加计数ct
3. 若x小于v[u],递归插入左子树;反之插入右子树。
4. 回溯时,若子节点优先级p大于当前节点p,则执行旋转。
删除void del(int &u, int x)1. 若x等于v[u]且计数ct[u] > 1,则直接减少计数。
2. 若计数为1,则需要删除节点:
a. 若为叶子节点或只有一个子节点,直接删除。
b. 若有两个子节点,则将优先级较高的子节点旋转上来,然后递归删除。
查排名int rk(int u, int x)1. 若x < v[u],向左子树递归。
2. 若x == v[u],排名为sz[l[u]] + 1
3. 若x > v[u],排名为sz[l[u]] + ct[u] + rk(r[u], x)
查第k大int kth(int u, int k)1. 若k <= sz[l[u]],在左子树中找。
2. 若sz[l[u]] < k <= sz[l[u]] + ct[u],答案就是v[u]
3. 否则,在右子树中找第k - sz[l[u]] - ct[u]大的数。
前驱/后继int pre(int u, int x)利用BST性质递归查找:前驱为严格小于x的最大值;后继为严格大于x的最小值。

案例研究:普通平衡树

题目描述

您需要动态地维护一个可重集合M MM,并且提供以下操作:

  1. M MM中插入一个数x xx
  2. M MM中删除一个数x xx。(若有多个相同的数,应只删除一个)
  3. 查询M MM中有多少个数比x xx小,并且将得到的答案加1 11
  4. 查询如果将M MM从小到大排列后,排名位于第x xx位的数。
  5. 查询M MMx xx的前驱(定义为M MM中小于x xx,且最大的数)。
  6. 查询M MMx xx的后继(定义为M MM中大于x xx,且最小的数)。

对于操作3 , 5 , 6 3,5,63,5,6不保证当前可重集中存在数x xx

对于操作4 , 5 , 6 4,5,64,5,6,保证答案一定存在。

输入格式

第一行为n nn,表示操作的个数,下面n nn行每行有两个数opt \text{opt}optx xxopt \text{opt}opt表示操作的序号($ 1 \leq \text{opt} \leq 6 $)。

输出格式

对于操作3 , 4 , 5 , 6 3,4,5,63,4,5,6每行输出一个数,表示对应答案。

输入输出样例 1
输入 1
10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598
输出 1
106465 84185 492737
【数据范围】

对于100 % 100\%100%的数据,1 ≤ n ≤ 10 5 1\le n \le 10^51n105∣ x ∣ ≤ 10 7 |x| \le 10^7x107

思路分析(旋转 Treap)

  1. 节点设计:每个节点包含左子、右子、值、出现次数、子树大小、随机优先级。子树大小用于快速求排名和第 k 小。
  2. 旋转操作:右旋和左旋用于维护堆性质(优先级大的在上)。旋转后需更新相关节点的子树大小。
  3. 插入:递归查找插入位置,若值已存在则增加计数;否则新建节点。插入后若子节点优先级大于父节点,则通过旋转将子节点上提。
  4. 删除:递归找到目标节点。若计数 >1 则减一;否则若只有一个孩子或没有孩子,则直接替换;若有两个孩子,则通过旋转将优先级大的孩子旋到当前节点位置,再递归删除该值(此时目标值已到旋转后的子树上)。
  5. 查询排名:根据 BST 性质,比较当前节点值与目标值,递归计算左子树大小并累加。
  6. 查询第 k 小:利用左子树大小判断目标所在区间,递归查找。
  7. 前驱/后继:递归查找小于 x 的最大值 / 大于 x 的最小值。

所有操作时间复杂度 O(log n),空间复杂度 O(n)。

代码实现

#include<bits/stdc++.h>usingnamespacestd;constintN=100010;constintINF=1e9+10;// 用于前驱后继的边界structNode{intl,r;// 左右子节点下标intv;// 节点值intc;// 该值出现次数ints;// 子树大小(含重复计数)intp;// 随机优先级}t[N];intrt,tot;// 根节点编号,节点总数// 更新节点 u 的子树大小voidupd(intu){t[u].s=t[t[u].l].s+t[t[u].r].s+t[u].c;}// 右旋(以 u 为轴,将左孩子旋上来)voidrot_r(int&u){intl=t[u].l;t[u].l=t[l].r;t[l].r=u;upd(u);upd(l);u=l;}// 左旋(以 u 为轴,将右孩子旋上来)voidrot_l(int&u){intr=t[u].r;t[u].r=t[r].l;t[r].l=u;upd(u);upd(r);u=r;}// 插入值 v,u 为当前子树根引用voidins(int&u,intv){if(!u){u=++tot;t[u]={0,0,v,1,1,rand()};return;}if(v==t[u].v){t[u].c++;upd(u);return;}if(v<t[u].v){ins(t[u].l,v);if(t[t[u].l].p>t[u].p)rot_r(u);}else{ins(t[u].r,v);if(t[t[u].r].p>t[u].p)rot_l(u);}upd(u);}// 删除一个值 v(只删一个)voiddel(int&u,intv){if(!u)return;if(v==t[u].v){if(t[u].c>1){t[u].c--;upd(u);return;}// 节点无孩子或只有一个孩子if(!t[u].l||!t[u].r){u=t[u].l+t[u].r;return;}// 两个孩子,将优先级大的孩子旋上来,然后递归删除if(t[t[u].l].p>t[t[u].r].p){rot_r(u);del(t[u].r,v);}else{rot_l(u);del(t[u].l,v);}upd(u);return;}if(v<t[u].v)del(t[u].l,v);elsedel(t[u].r,v);upd(u);}// 查询比 v 小的数的个数 +1(即 v 的排名)intrk(intu,intv){if(!u)return1;// 空树,v 排名为 1if(v<=t[u].v)returnrk(t[u].l,v);returnt[t[u].l].s+t[u].c+rk(t[u].r,v);}// 查询排名为 k 的数(k 从 1 开始)intkth(intu,intk){while(u){if(k<=t[t[u].l].s)u=t[u].l;elseif(k<=t[t[u].l].s+t[u].c)returnt[u].v;else{k-=t[t[u].l].s+t[u].c;u=t[u].r;}}return0;// 题目保证有解}// 查询 v 的前驱(小于 v 的最大数)intpre(intu,intv){intres=-INF;while(u){if(v<=t[u].v)u=t[u].l;else{res=max(res,t[u].v);u=t[u].r;}}returnres;}// 查询 v 的后继(大于 v 的最小数)intsuc(intu,intv){intres=INF;while(u){if(v>=t[u].v)u=t[u].r;else{res=min(res,t[u].v);u=t[u].l;}}returnres;}intmain(){srand(time(0));intn;scanf("%d",&n);while(n--){intop,x;scanf("%d%d",&op,&x);if(op==1)ins(rt,x);elseif(op==2)del(rt,x);elseif(op==3)printf("%d\n",rk(rt,x));elseif(op==4)printf("%d\n",kth(rt,x));elseif(op==5)printf("%d\n",pre(rt,x));elseif(op==6)printf("%d\n",suc(rt,x));}return0;}

功能分析

  • 插入 (ins):递归插入,若值已存在则增加计数;否则新建节点。通过旋转维持大根堆性质,保证树高期望 O(log n)。
  • 删除 (del):递归删除,若计数 >1 则减一;若只有一个孩子则直接替换;若有两个孩子则通过旋转将优先级大的孩子旋到当前节点,再递归删除目标值。
  • 查询排名 (rk):利用 BST 性质,递归统计比 x 小的节点个数,最终结果加 1 即为排名。
  • 查询第 k 小 (kth):根据左子树大小与当前节点计数决定往左、返回当前、或往右查找。
  • 前驱 (pre):循环查找小于 x 的最大值。
  • 后继 (suc):循环查找大于 x 的最小值。

更多系列知识,请查看专栏:《信奥赛C++提高组csp-s知识详解及案例实践》:
https://blog.csdn.net/weixin_66461496/category_13113932.html


各种学习资料,助力大家一站式学习和提升!!!

#include<bits/stdc++.h>usingnamespacestd;intmain(){cout<<"########## 一站式掌握信奥赛知识! ##########";cout<<"############# 冲刺信奥赛拿奖! #############";cout<<"###### 课程购买后永久学习,不受限制! ######";return0;}

1、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

2、csp信奥赛冲刺一等奖有效刷题题解:

信奥赛C++普及组csp-j初赛&复赛真题题解(持续更新)https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html

3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转


GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html

4、csp/信奥赛C++,完整信奥赛系列课程(永久学习):

https://edu.csdn.net/lecturer/7901 点击跳转

· 文末祝福 ·

#include<bits/stdc++.h>usingnamespacestd;intmain(){cout<<"跟着王老师一起学习信奥赛C++";cout<<" 成就更好的自己! ";cout<<" csp信奥赛一等奖属于你! ";return0;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/30 9:45:37

哔哩下载姬完整使用教程:3分钟掌握B站视频高效下载与管理技巧

哔哩下载姬完整使用教程&#xff1a;3分钟掌握B站视频高效下载与管理技巧 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔哩哔哩网站视频下载工具&#xff0c;支持批量下载&#xff0c;支持8K、HDR、杜比视界&#xff0c;提供工具箱&#xff08;音视频提取、去水印等…

作者头像 李华
网站建设 2026/5/30 9:40:26

机器人+AI如何重塑医疗美容:从精准手术到个性化康复的技术融合

1. 从按摩到手术&#xff1a;一场静默的技术革命 如果你最近去过一些高端的皮肤管理中心或者大型医院的康复科&#xff0c;可能会发现一些微妙的变化&#xff1a;操作台上多了一只机械臂&#xff0c;或者屏幕上显示着由算法生成的个性化治疗方案。这不再是科幻电影里的场景&…

作者头像 李华
网站建设 2026/5/30 9:36:56

GEAK框架:LLM驱动的Triton GPU内核生成技术解析

1. GEAK框架&#xff1a;LLM驱动的Triton GPU内核生成革命 在AMD Instinct™MI300X这类现代GPU上开发高性能计算内核&#xff0c;传统上需要开发者同时具备硬件架构知识和底层编程技巧。我曾参与过一个深度学习推理优化项目&#xff0c;团队花费两周手工编写的Triton内核&#…

作者头像 李华