k短路的可持久化可并堆做法
例题:洛谷P2483 【模板】k短路 / [SDOI2010]魔法猪学院
传统的 算法在图为一个 元环的时候每次找一条路都要转整整一圈,复杂度会达到
而可持久化可并堆的做法在找路径的时候与 无关,找到一条路的复杂度是
定义
在一张有向带权图 中,从起点 到终点 的不严格递增的第 短路的长度。两条路径不同定义为按顺序经过的边集不同。
一些性质
记 为 到 的最短距离,设 为以 为根用反向边建出的最短路径树。
对于一条起点为 ,终点为 ,权值为 的边 ,定义 ,表示选择这条边和最短路径的差。
那么对于一条路径 ,它的长度
记 为路径 去掉与 的交集后的边集,即路径中的非树边,那么有:
- 中相邻的两条边 ,满足 是 在 上的祖先,或 。
- 对于不同的路径 ,其对应的 是唯一的。(树上两点路径是唯一的)
- . 因为树边的
由后两条性质, 短路问题可以转化为求前 小的 , 由非树边组成,满足第一条性质。
求解
考虑动态地生成可能的 。
维护一个权值由小到大的优先队列,初始时只有一个空序列。
每次取出队头,即找到的第 小的路径,然后在此基础上加入新的非树边。
先考虑朴素的加边方法:
每次从队头取出一个序列 ,设 的最后一条边的终点为 ,加入一条非树边 满足 的起点是 在树上的祖先,一次性将所有可能的边加入。
重复 次。
然而每次都将可能的边全部加入显然不现实,复杂度会达到
注意到一个点拓展出的 是有大小顺序的,我们可以每次只拓展 最小的那条边,与之相应的,要加入“替换最后一条边” 的操作
考虑维护出一个有序表 ,按权值由小到大记录所有起点是 的祖先的非树边 。
对于每次从队头取出的序列 ,设其最后一条边是 ,终点为 ,倒数第二条边的终点为 ,那么有两种产生新的 的方式:
- 加入 的第一条边
- 将 替换为 中 的后一条边
这样做可以保证取出的队头一定是当前的最小路径,因为所有还未生成的 的权值一定大于等于队列中某条路径;并且产生的路径不会重复。
薅一张图:
显然可以通过 加上所有起点为 的非树边得到,于是可以采用可持久化可并堆。
只不过实现上有点讲究,因为我们需要找到 中 的后一条边,所以实际上一条路径表示为了一个 ,第一维是这条路径的 ,第二维是这条路径的最后一条边在 的堆中的哪个位置。堆里存的是边,键值为 的大小,要记录边的终点。
那么替换最后一条边就可以表示为将第二维换为堆中它的两个儿子(都要加入),加入一条新边就可以表示为将第二维跳到当前边对应终点的堆顶。
取出 次队头即找到了 短路。
复杂度
如果把起点为 的非树边单独建一个堆,把它当成一个整体元素 ,把它插入 得到 ,那么建 的总复杂度是 (相信大家一定会线性建堆 ),建 的总复杂度变为
只不过替换最后一条边的操作要变成将第二维替换为 中的儿子以及 中的儿子,在 上有个 4 的常数。
于是除去最短路的复杂度是 的。(虽然最短路一般就是 …)