左偏树merge函数必须递归合并右子树,因左偏性质保证右链长≤log(n+1)−1,确保o(logn)复杂度;若合并左子树或随机选边,将破坏距离约束致最坏o(n)。

左偏树 merge 函数必须递归合并右子树
左偏树的合并效率能稳定在 O(log n),关键在于每次只递归操作右链——因为左偏性质保证了右子树距离更小,整条右链长度不超过 log(n+1)-1。如果你写成「合并左子树」或「随机选一边合并」,就会破坏距离约束,最坏退化到 O(n)。
标准实现中,merge 的逻辑是:先确保根值更小的节点为新根(维持堆性),然后固定将另一个堆与当前根的右子树合并;之后检查左右子树距离,若左 dis dis 就交换子树(维持左偏性);最后更新根的 dis = dis[rs] + 1。
- 别漏掉
if(!x || !y) return x + y;这句边界处理,否则空指针解引用直接崩 - 交换节点时用
swap(x, y)而非手动赋值,避免写反父子关系 -
dis数组初始必须全设为 0(空节点距离定义为 0,不是 -1;多数模板用 0 更稳妥)
建树不能用 for 循环逐个 push,得用队列两两合并
如果对一个数组 a[1..n] 建左偏树,错误做法是新建 n 个单点树,再循环调用 merge(root, new_node) ——这会变成 O(n log n),且右链被反复拉长,实际性能远不如 priority_queue。
正确建法是把每个单点视为一棵左偏树,丢进队列,每次取队首两个合并,结果入队尾,直到只剩一棵。这样合并次数是 n−1 次,每轮合并均摊 O(1),总复杂度 O(n)。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 别用 vector 模拟队列,用
queue<int></int>更清晰 - 合并后记得更新新根的
fa(如果需要路径压缩或查所属堆) - 若后续要支持「删除任意节点」,建树阶段就该维护
fa[i]和当前是否在堆中(比如用vis[i]或设val[i] = INF标记)
合并时 swap 左右子树必须在递归 merge 之后、更新 dis 之前
顺序错会导致 dis 计算错误。典型错误写法:dis[x] = dis[rs[x]] + 1 放在 swap 前面,而此时 rs[x] 还没被交换,取的是旧右子树的距离;但你刚把左子树换成了右子树,实际应取交换后的新右子树距离。
正确顺序只能是:递归合并 → 检查并 swap(如有必要)→ 再读 rs[x] 更新 dis[x]。
- 别依赖编译器优化,显式写
int &r = rs[x]; int &l = ls[x];再操作,避免指针悬空 - 如果用了结构体封装(如
struct Node { int val, dis, l, r; } tr[N];),注意所有索引都基于下标而非指针,避免越界 - 调试时可在 merge 开头加
assert(val[x] 快速捕获堆性破坏
delete 任意节点不能只断开连接,必须 merge 其左右子树再接回
左偏树没有“懒删除”概念。删节点 x 时,不能只把它从父节点断开就完事——它的左右子树仍需保持左偏树结构,并重新挂到原位置的父节点上(或作为新堆根)。否则后续 merge 会因子树不满足左偏性而崩溃。
标准做法是:先 int new_root = merge(ls[x], rs[x]);,再把 new_root 接到 fa[x] 对应侧(左/右),最后更新 fa[new_root] = fa[x];若 x 是原堆根,则整个堆根变为 new_root。
- 别忘了清空
ls[x] = rs[x] = 0,防止后续误用已删节点 - 如果用了并查集维护连通块(如 P2713 题),删节点后要调用
fa[x] = find(fa[x])更新其代表元,否则 find 会指向已删节点 - multiset 维护全局最大值时,删节点前必须先从 multiset 中 erase 对应迭代器(不能 erase 值),否则重复值全被删掉
dis 更新时机,都卡在左偏性和堆性的交界上。写错一处,可能当场段错误,也可能跑得慢但答案对——后者更危险,容易在线上评测时超时。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










