k短路的可持久化可并堆做法

例题:洛谷P2483 【模板】k短路 / [SDOI2010]魔法猪学院

俞鼎力神仙的PPT

传统的 AA^* 算法在图为一个 nn 元环的时候每次找一条路都要转整整一圈,复杂度会达到 O(nk)O(nk)
而可持久化可并堆的做法在找路径的时候与 nn 无关,找到一条路的复杂度是 O(logk)O(\log k)

定义

在一张有向带权图 GG 中,从起点 ss 到终点 tt 的不严格递增的第 kk 短路的长度。两条路径不同定义为按顺序经过的边集不同。

一些性质

dis[i]dis[i]iitt 的最短距离,设 TT 为以 tt 为根用反向边建出的最短路径树。

对于一条起点为 uu,终点为 vv,权值为 ww 的边 ee,定义 δ(e)=w+disvdisu\delta(e)=w+dis_v-dis_u,表示选择这条边和最短路径的差。

那么对于一条路径 PP,它的长度 len(P)=diss+ePδ(e)len(P)=dis_s+\sum_{e\in P}\delta(e)

PP' 为路径 PP 去掉与 TT 的交集后的边集,即路径中的非树边,那么有:

  • PP' 中相邻的两条边 (ab),(cd)(a\to b),(c\to d),满足 ccbbTT 上的祖先,或 c=bc=b
  • 对于不同的路径 PP,其对应的 PP' 是唯一的。(树上两点路径是唯一的)
  • len(P)=len(P)len(P)=len(P') . 因为树边的 δ(e)=0\delta(e)=0

由后两条性质,kk 短路问题可以转化为求前 kk 小的 PP'PP' 由非树边组成,满足第一条性质。

求解

考虑动态地生成可能的 PP'
维护一个权值由小到大的优先队列,初始时只有一个空序列。

每次取出队头,即找到的第 ii 小的路径,然后在此基础上加入新的非树边。

先考虑朴素的加边方法:
每次从队头取出一个序列 qq,设 qq 的最后一条边的终点为 vv,加入一条非树边 ee 满足 ee 的起点是 vv 在树上的祖先,一次性将所有可能的边加入。
重复 kk 次。
然而每次都将可能的边全部加入显然不现实,复杂度会达到 O(kmlogm)O(km\log m)

注意到一个点拓展出的 ee 是有大小顺序的,我们可以每次只拓展 δ(e)\delta(e) 最小的那条边,与之相应的,要加入“替换最后一条边” 的操作

考虑维护出一个有序表 g(v)g(v),按权值由小到大记录所有起点是 vv 的祖先的非树边 ee
对于每次从队头取出的序列 qq,设其最后一条边是 ee,终点为 vv,倒数第二条边的终点为 uu,那么有两种产生新的 PP' 的方式:

  • 加入 g(v)g(v) 的第一条边
  • ee 替换为 g(u)g(u)ee 的后一条边

这样做可以保证取出的队头一定是当前的最小路径,因为所有还未生成的 PP' 的权值一定大于等于队列中某条路径;并且产生的路径不会重复。
薅一张图:
k短路的可持久化可并堆做法

g(v)g(v) 显然可以通过 g(v)g(v在树上的父亲) 加上所有起点为 vv 的非树边得到,于是可以采用可持久化可并堆。

只不过实现上有点讲究,因为我们需要找到 g(u)g(u)ee 的后一条边,所以实际上一条路径表示为了一个 pairpair,第一维是这条路径的 δ(e)\sum \delta (e),第二维是这条路径的最后一条边在 uu 的堆中的哪个位置。堆里存的是边,键值为 δ\delta 的大小,要记录边的终点。

那么替换最后一条边就可以表示为将第二维换为堆中它的两个儿子(都要加入),加入一条新边就可以表示为将第二维跳到当前边对应终点的堆顶。

取出 kk 次队头即找到了 kk 短路。
复杂度 O(mlogm+klogk)O(m\log m+k\log k)

如果把起点为 vv 的非树边单独建一个堆,把它当成一个整体元素 h(v)h(v),把它插入 g(v)g(v的父亲) 得到 g(v)g(v),那么建 h(v)h(v) 的总复杂度是 O(m)O(m)相信大家一定会线性建堆 ),建 g(v)g(v) 的总复杂度变为 O(nlogn)O(n\log n)
只不过替换最后一条边的操作要变成将第二维替换为 hh 中的儿子以及 gg 中的儿子,在 klogkk\log k 上有个 4 的常数。
于是除去最短路的复杂度是 O(m+klogk)O(m+k\log k) 的。(虽然最短路一般就是 O(mlogm)O(m\log m) …)