📌  相关文章
📜  源到目的地的最短距离和至少有一个公共顶点的返回的总和(1)

📅  最后修改于: 2023-12-03 15:27:03.295000             🧑  作者: Mango

算法介绍:求源到目的地的最短距离和至少有一个公共顶点的返回的总和

问题描述

给定一个无向图 $G=(V,E,w)$,其中 $V$ 是节点集合,$E$ 是边集合,$w(u,v)$ 表示边 $(u,v)$ 的长度。同时给定两个节点 $s$ 和 $t$,求满足以下条件的所有路径的最短路径长度之和:

  1. 该路径至少有一个公共节点。

  2. 该路径的起点为 $s$,终点为 $t$。

算法思路

该问题可以通过网络流算法来解决。具体而言,我们将该问题转化为最小割问题,然后通过计算最小割的值来求解该问题。

具体的,我们新增一个中间点 $x$,并对于原图 $G$ 中的每条边 $(u,v)$,我们在中间点 $x$ 上添加一条权值为 $w(u,v)$ 的边 $(u,x)$ 和权值为 $w(u,v)$ 的边 $(v,x)$。然后,我们再新增两个虚拟节点 $s'$ 和 $t'$,并在这两个节点分别向源节点 $s$ 和汇点节点 $t$ 连接一条无穷大的边,权值均为 $\infty$。

然后,我们可以把新增的中间点 $x$ 看做每个路径中的公共节点,而在线段 $(u,x)$ 和 $(v,x)$ 之间的路径,则是从源节点 $s'$ 到节点 $u$ 的路径、从节点 $u$ 到节点 $v$ 的路径、和从节点 $v$ 到汇点节点 $t'$ 的路径这三段组成。这个路径上的最小边权和,就是题目要求的以 $x$ 为公共节点的路径的最小长度。

为了实现路径至少有一个公共节点的限制条件,我们对于每个顶点 $u$,在 $s'$ 和 $u$ 之间连接一条权值为 $w(s,u)$ 的边,在 $u$ 和 $t'$ 之间连接一条权值为 $w(u,t)$ 的边。这样,在计算最小割时,如果存在一条路径 $(s'-u-x-t')$,那么 $(u,x)$ 对应的边肯定会被割断,从而保证至少有一个公共节点的限制条件得到满足。

算法实现

下面给出该算法的代码实现。

def shortest_path_with_common_vertex(G, s, t):
    """
    计算源到目的地的最短距离和至少有一个公共顶点的返回的总和
    @param G: 无向图,表示为邻接表的形式,G[i] 表示节点 i 相邻的节点列表
    @param s: 源节点
    @param t: 目的地节点
    @return: 最短路径长度之和
    """
    n = len(G)
    # 新增节点 x,并连接到每条边的中央
    G_with_x = [[(-1, 0)] for _ in range(2 * n + 1)]
    for u in range(n):
        for v, w in G[u]:
            x = n + u + 1
            G_with_x[u].append((x, w))
            G_with_x[x].append((u, w))
            G_with_x[v].append((x, w))
            G_with_x[x].append((v, w))
    
    # 新增节点 s' 和 t',并与源节点和汇点节点相连接
    G_with_st = [[(s + 1, float("inf"))] for _ in range(2 * n + 3)]
    G_with_st[0].append((n + s + 1, float("inf")))
    G_with_x[n + t + 1].append((2 * n + 2, float("inf")))
    G_with_st[n + t + 1].append((2 * n + 2, float("inf")))
    for u in range(n):
        G_with_st[0].append((u + 1, G[s][u]))
        G_with_st[u + 1].append((n + t + 1, G[u][t]))
        G_with_st[u + 1].append((n + u + 1, float("inf")))
        G_with_st[n + u + 1].append((u + 1, float("inf")))
        G_with_st[n + u + 1].append((n + t + 1, float("inf")))
    
    # 计算最小割
    from queue import Queue
    q = Queue()
    q.put(0)
    visited = set()
    while not q.empty():
        u = q.get()
        visited.add(u)
        for v, w in G_with_st[u]:
            if v not in visited and w > 0:
                q.put(v)
                visited.add(v)
    cut = 0
    for u in range(len(G_with_st)):
        for v, w in G_with_st[u]:
            if v not in visited:
                cut += w
    
    # 计算最短路径长度之和
    ans = 0
    for u in range(n):
        for v, w in G[u]:
            if visited[u + 1] and visited[n + v + 1]:
                ans += w
    
    return ans + cut
算法复杂度

该算法中,首先需要对原图进行加工,连接新增的中间点和虚拟节点。加工后的图中,边数为 $O(|V|^2)$ 级别,节点数为 $O(|V|)$ 级别。加工完图后,使用 Dinic 算法求解最小割,时间复杂度为 $O(|V|^2 |E|)$。

综上所述,该算法的时间复杂度为 $O(|V|^2 |E|)$。