可持久化可并堆

可持久化可并堆一般用于求解 kk 短路问题.

如果一种可并堆的时间复杂度不是均摊的,那么它在可持久化后单次操作的时间复杂度就保证是 O(log⁡n)O(\log n) 的,即不会因为特殊数据而使复杂度退化.

可持久化左偏树

在学习本内容前,请先了解 左偏树 的相关内容.

过程

回顾左偏树的合并过程,假设我们要合并分别以 x,yx,y 为根节点的两棵左偏树,且维护的左偏树满足小根堆的性质:

  1. 如果 x,yx,y 中有结点为空,返回 x+yx+y.

  2. 选择 x,yx,y 两结点中权值更小的结点,作为合并后左偏树的根.

  3. 递归合并 xx 的右子树与 yy,将合并后的根节点作为 xx 的右儿子.

  4. 维护当前合并后左偏树的左偏性质,维护 dist 值,返回选择的根节点.

由于每次递归都会使 dist[x]+dist[y] 减少一,而 dist[x] 是 O(log⁡n)O(\log n) 的,一次最多只会修改 O(log⁡n)O(\log n) 个结点,所以这样做的时间复杂度是 O(log⁡n)O(\log n) 的.

可持久化要求保留历史信息,使得之后能够访问之前的版本.要将左偏树可持久化,就要将其沿途修改的路径复制一遍.

所以可持久化左偏树的合并过程是这样的:

  1. 如果 x,yx,y 中有结点为空,返回 x+yx+y.

  2. 选择 x,yx,y 两结点中权值更小的结点,新建该结点的一个复制 pp,作为合并后左偏树的根.

  3. 递归合并 pp 的右子树与 yy,将合并后的根节点作为 pp 的右儿子.

  4. 维护以 pp 为根的左偏树的左偏性质,维护其 dist 值,返回 pp.

由于左偏树一次最多只会修改并新建 O(log⁡n)O(\log n) 个结点,设操作次数为 mm,则可持久化左偏树的时间复杂度和空间复杂度均为 O(mlog⁡n)O(m\log n).

参考实现

int merge(int x, int y) {
  if (!x || !y) return x + y;
  if (v[x] > v[y]) swap(x, y);
  int p = ++cnt;
  lc[p] = lc[x];
  v[p] = v[x];
  rc[p] = merge(rc[x], y);
  if (dist[lc[p]] < dist[rc[p]]) swap(lc[p], rc[p]);
  dist[p] = dist[rc[p]] + 1;
  return p;
}

本文内容来自 OI Wiki《可持久化可并堆》;作者与修订历史见原文。AIOJ 对排版、站内链接和资源路径做了格式适配。

内容依 CC BY-SA 4.0 与 SATA 许可共享;附加条款以原文声明为准。感谢 OI Wiki 作者与贡献者;请按 SATA 约定前往 OI Wiki 仓库 Star 支持。