K短路
K短路
A*算法
这是一个假算法
先从起点到终点跑一遍最短路,然后估价函数直接设为 当前距离+到终点距离,然后直接上A*
即可
可持久化可并堆
这里拿有向边举例,无向边则看成两条有向边处理
首先,我们先建反边从终点开始跑一遍最短路,求出每个点到终点的最短路和通往这个点的边,然后把所有这些有向边的反边(即原图的正向边)拿出来构成一棵最短路树(即图的一棵生成树,满足每个点到根的距离都是原图中最短路的长度),记为T
,这棵树有一些性质:
- 令一条从起点到终点的路径为
P
,去掉这条路径中在T
中的边得到P'
,对于P'
中任意两条相邻的边,一条边的起点一定是另一条边的终点在T
上的祖先(包括自己)
证明:一条路径由一些树边和一些非树边组成(显然),因为这些树边都是向根方向的有向边,所以它的终点是起点的祖先,如果我们把连在一起的树边看成一条边,那么每一条树边如果不是第一条或最后一条,它连接的一定是两条在
P'
中相邻的非树边,其中一条的起点是这条树边的终点,另一条的终点是这条树边的起点,所以一条的起点一定是另一条的终点的祖先。但是当两条非树边相邻时,一条的起点与另一条的终点是同一个点,所以这个祖先可以是自己
- 对于任意一个满足性质一的边集
S
,有且仅有一条路径P
满足P'=S
由于树上两点之间路径是唯一的,所以这个不需要证明
- 定义
W
为选择一条非树边路径与选择树边路径的距离差(后文记为额外权值),那么一条路径的长度为所有非树边的W
与从起点到终点的距离之和
简单暴力
下面所有提到的祖先均包括自己
根据上面的性质,我们可以维护一个堆,里面存放一些路径,根据性质二和性质三,对于每一条路径我们只需要保留所有的非树边和额外权值和
每次我们取出一条额外权值最小的路径,用额外权值与起点到终点的距离的和更新答案,然后对这条路径进行扩展
实际上,我们不需要将边集完全储存下来,只需要储存这条路径的最后一条非树边和这条路径的额外权值和
考虑如何扩展:
-
为了保证不重,我们只针对最后一条非树边扩展,即可以在这条边的终点的任意祖先处取一条权值尽量小的边接在后面表示在之前路径的基础上,去掉最后到达终点的树边的一部分,并从去掉以后到达的地方走一条非树边然后再走树边到达终点,为了保证每次更新出来的路径一定包含能够更新出的路径中额外权值最小的那一条,我们选择额外权值最小的更新
-
但是,因为我们每次更新的都是尽量小的,在
k
比较大的情况下,有些额外权值稍微大一点的路径也有可能会对答案产生影响,所以我们还要进行一种扩展,即删去最后一条非树边,在被删去的这条非树边的起点的祖先处添加一条权值大于这一条且尽量小的边
这样看似不可做,我们每次都需要扫一遍祖先,然后进行很多查询,复杂度肯定爆炸
优化
上述算法的瓶颈在于不重复地找到起点相同且权值大于这条边的一条边和所有祖先权值最小的出边
我们可以想到对每个节点开一个堆,对于第一种扩展,我们直接取当前这条边在堆上的两个儿子进行更新,显然这样可以满足不重复地要求(直接这样并没有保证不漏),由于这样相当于是对于这个堆按权值遍历,所以也可以满足一定会更新出额外权值恰好大于这条边的边;对于第二种扩展,我们可以使每个节点的堆从父亲处继承(正确性在下面证明),然后直接用最后一条边的终点所在的堆的堆顶扩展
为什么需要从父亲出继承堆呢?我们知道,在树上任意向上移动到祖先节点,额外权值都是不变的,由于我们在对第一种扩展的时候,并没有考虑在删去边的起点的祖先处(不包括自己)加边,所以这个被漏掉了,我们把堆从父亲处继承,刚好可以把这个加上,做到不漏
总体思路
首先建反边从终点跑一遍最短路求出最短路径树并将节点x
的距离记为 \(dis_x\),然后在这颗树上遍历所有节点,每个节点用可持久化可并堆从父亲处获得一份副本,然后枚举所有出边[x->y, val]
(正向边),这条出边的额外权值为 \(dis_y+val-dis_x\),加入堆里
使k
减去1
,然后将起点堆顶加入优先队列,循环k
次,每次将队首节点取出,加入这个节点的两个儿子,每个儿子的额外权值分别为当前取出来的路径的额外权值减去取出节点的额外权值加上儿子的额外权值,然后找到取出节点表示的边的终点,加入这个点的堆顶,额外权值为当前取出路径的额外权值加上这个节点的额外权值,最后答案即为最后一次取出来的路径的额外权值加上从起点到终点的最短路径长度