首先我们可以知道,在边权和最小的子图中的任意两个点 u 和 v,如果 u 可以到达 v,那么从 u 到 v 一定只有唯一的一条简单路径。这是因为如果存在多条路径,我们也只会走最短的那一条,因此不在最短路径上的那些边都是无意义的,我们可以将它们移除。
这样一来:
从 src}_1 到 dest 有唯一的一条简单路径,记为 X;
从 src}_2 到 dest 有唯一的一条简单路径,记为 Y。
假设 X 上第一个与 Y 共有的节点为 c,显然这样的 c 是一定存在的,因为 dest 就是 X 和 Y 的一个共有节点(但可能存在更早的共有节点)。我们可以断定,从 c 开始到 dest 结束的这一部分,在 X 和 Y 中一定是完全相同的,这是因为 c 到 dest 一定只有唯一的一条简单路径,因此 X 和 Y 必定共有这条路径。因此,整个子图可以看成是三部分的并集:
从 src}_1 到 c 的一条简单路径;
从 src}_2 到 c 的一条简单路径;
从 c 到 dest 的一条简单路径。
此时子图的边权和即为这三部分的边权和之和,即子图呈现「Y 型」。如果 c 与 src}_1,src}_2 或者 dest 中的某个节点重合,那么也是满足要求的。
思路与算法
我们可以枚举 c 来得到边权和最小的子图。此时,我们就需要计算出「从 src}_1 到 c」「从 src}_2 到 c」「从 c 到 dest」这三部分的最短路径。
对于「从 src}_1 到 c」「从 src}_2 到 c」这两部分而言,我们可以使用两次 Dijkstra 算法计算出以 src}_1 为出发点和以 src}_2 为出发点,到所有节点的最短路径长度。而对于「从 c 到 dest」这一部分,我们可以将原图中的所有边反向,这样就变成了「从 dest 到 c」。我们就可以使用 Dijkstra 算法计算出以 dest 为出发点,到所有节点的最短路径长度。
while q: u = heapq.heappop(q)[1] if u in used: continue used.add(u) for (v, weight) in graph[u]: target = dist[u] + weight if dist[v] == -1or target < dist[v]: dist[v] = target heapq.heappush(q, (dist[v], v)) return dist dist1, dist2, dist3 = dijkstra(g, src1), dijkstra(g, src2), dijkstra(grev, dest) ans = -1 for i inrange(n): if dist1[i] != -1and dist2[i] != -1and dist3[i] != -1: result = dist1[i] + dist2[i] + dist3[i] if ans == -1or result < ans: ans = result return ans