左偏树

一种可并堆,支持高效合并

dis[x] 为从节点x出发可以向右走的最大步数,左偏性质为满足任意节点左儿子的 dis 不小于右儿子的 dis

定义

  • 外节点为只有不超过 \(1\) 个儿子的节点,
  • 距离为一个节点到离它最近的外节点的路径的权值和,外节点距离为 \(0\),空节点距离为 \(-1\)
  • 左偏树的距离为其根节点的距离

存储

每个节点存储:

  • val:权值
  • lr:左右儿子
  • dis:距离
  • f:父亲

操作

merge

合并两个左偏堆,返回合并后的根节点

merge(x,y) 流程:(xy 为根节点)

  1. 若其一为空,则返回另一个
  2. val[x]>val[y],则交换 x,y
  3. r[x]y 合并,并将 r[x] 设为合并后的根节点
  4. dis[r[x]]>dis[l[x]] 则交换两个儿子
  5. 更新 dis[x]
  6. 返回 x

时间复杂度为 \(O(1)\)\(O(\log n)\)

top

返回堆顶,即根节点权值,时间复杂度 \(O(1)\)

del

删除根节点

del(x) 流程:

  1. 临时变量备份左右子树下标
  2. 清空根节点的权值(设为 -1,不清除问题也不大)和左右儿子指针
  3. 合并左右子树

remove

删除任意节点(任意编号的节点,一般的可合并堆不支持删除给定权值的节点)

remove(x) 流程:

  1. 类似 del 操作,删去自己并合并两个儿子
  2. 若有父节点,则比较其两个儿子的 dis,若需要交换则交换,并跟新其 dis;否则结束
  3. 若父节点 dis 不变,则结束
  4. 否则继续向上跟新

时间复杂度 \(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)];
}

参考

  1. 【笔记】左偏树
  2. 【学习笔记】左偏树
  3. oi-wiki 左偏树
posted @ 2024-11-14 07:09  Hstry  阅读(21)  评论(0)    收藏  举报