左偏树
一种可并堆,支持高效合并
令 dis[x] 为从节点x出发可以向右走的最大步数,左偏性质为满足任意节点左儿子的 dis 不小于右儿子的 dis
定义
- 外节点为只有不超过 \(1\) 个儿子的节点,
- 距离为一个节点到离它最近的外节点的路径的权值和,外节点距离为 \(0\),空节点距离为 \(-1\)
- 左偏树的距离为其根节点的距离
存储
每个节点存储:
val:权值l,r:左右儿子dis:距离f:父亲
操作
merge
合并两个左偏堆,返回合并后的根节点
merge(x,y) 流程:(x,y 为根节点)
- 若其一为空,则返回另一个
- 若
val[x]>val[y],则交换x,y - 将
r[x]与y合并,并将r[x]设为合并后的根节点 - 若
dis[r[x]]>dis[l[x]]则交换两个儿子 - 更新
dis[x] - 返回
x
时间复杂度为 \(O(1)\) 到 \(O(\log n)\)
top
返回堆顶,即根节点权值,时间复杂度 \(O(1)\)
del
删除根节点
del(x) 流程:
- 临时变量备份左右子树下标
- 清空根节点的权值(设为
-1,不清除问题也不大)和左右儿子指针 - 合并左右子树
remove
删除任意节点(任意编号的节点,一般的可合并堆不支持删除给定权值的节点)
remove(x) 流程:
- 类似
del操作,删去自己并合并两个儿子 - 若有父节点,则比较其两个儿子的
dis,若需要交换则交换,并跟新其dis;否则结束 - 若父节点
dis不变,则结束 - 否则继续向上跟新
时间复杂度 \(O(\log n)\)
需要额外保存真正的父亲
全堆操作
若不改变相对大小关系(加减,乘非负整数,除以正整数),则直接在根打标记即可。否则需要重构整个堆
总代码
数组版:
int l[], r[], f[];//左右儿子和父亲(注:此处的父亲为某个祖先,不一定要是真正的父亲,用并查集维护以快速找到堆顶)
int val[], ds[];//权值和左偏树的 `ds`
int find(int x){
return x == f[x]? x : f[x] = find(f[x]);
}
int merge(int x, int y){
if (!x || !y)return x | y;
if (val[x] > val[y])swap(x, y);
r[x] = merge(r[x], y);
if (ds[l[x]] < ds[r[x]])swap(l[x], r[x]);
ds[x] = ds[r[x]] + 1;
f[l[x]] = f[r[x]] = f[x] = x;
return x;
}
void del(int x){
val[x] = -1;
f[l[x]] = l[x], f[r[x]] = r[x];
f[x] = merge(l[x], r[x]);
}
int top(int x){
return val[find(x)];
}

浙公网安备 33010602011771号