一、题目
快递公司每日早晨,给每位快递员推送需要淡到客户手中的快递以及路线信息,快递员自己又查找了一些客户与客户之间的路线距离信息,请你依据这些信息,给快递员设计一条最短路径,告诉他最短路径的距离。
不限制快递包裹送到客户手中的顺序,但必须保证都送到客户手中;
用例保证一定存在投递站到每位客户之间的路线,但不保证客户与客户之间有路线,客户位置及投递站均允许多次经过;
所有快递送完后,快递员需回到投递站
二、输入
首行输入两个正整数n、m.
接下来n行,输入快递公司发布的
客户快递信息,格式为:
客户id 投递站到客户之间的距离distance
再接下来的m行,是快递员自行查找的
客户与客户之间的距离信息,
格式为:客户1id 客户2id distance
在每行数据中,数据与数据之间均
以单个空格分割规格:
0 <=n <= 10 0 <= m <= 10 0 < 客户id <= 1000 0 < distance <= 10000
三、输出
最短路径距离,如无法找到,请输出-1
四、示例
示例一
输入:
2 1
1 1000
2 1200
1 2 300
输出:
2500
- 说明: 快递员先把快递送到客户1手中,接下来直接走客户1到客户2之间的直通线路,最后走投递站和客户2之间的路,回到投递站,距离为1000+300+1200= 2500
示例二
输入:
5 1
5 1000
9 1200
17 300
132 700
500 2300
5 9 400
输出:
9200
五、题解
- Floyd-Warshall算法求无向图任意 2 点最小距离 + 动态规划求解走过所有节点的最小距离
- 输入参数中客户节点数不是 1,2,3 递增的。需要映射转换为数组索引
- 难点是动态规划状态表示 dp[i][j],走过 i 状态表示的节点,当前在 j 节点时走过的最小距离。需要注意的是对于 dp[2][1]表示走过第 1 个客户节点(二进制表示为 10),当前在第 1 号节点,值为 dist[0][1],此时状态值并不包括 0 号节点
- 动态规划中求返回 0 号节点最值是状态
(1 << (n + 1)) - 2
- 各种 ChatGPT都做出来的是错误的题解,可能跟网上没有完整的答案有关系
5.1 Java 实现
package org.stone.study.algo.ex202411;
import java.util.*;
import java.io.*;
/**
*
* 快递公司每日早晨,给每位快递员推送需要淡到客户手中的快递以及路线信息,快递员自己又查找了一些客户与客户之间的路线距离信息,请你依据这些信息,给快递员设计一条最短路径,告诉他最短路径的距离。
* 不限制快递包裹送到客户手中的顺序,但必须保证都送到客户手中;
* 用例保证一定存在投递站到每位客户之间的路线,但不保证客户与客户之间有路线,客户位置及投递站均允许多次经过;
* 所有快递送完后,快递员需回到投递站
* 示例输入:
5 1
5 1000
9 1200
17 300
132 700
500 2300
5 9 400
输出:
9200
*/
public class FloydWarshall {
public static final int INF = Integer.MAX_VALUE / 2;
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
String[] line = scanner.nextLine().split(" ");
int n = Integer.parseInt(line[0]);
int m = Integer.parseInt(line[1]);
Map<Integer, Integer> custToIndex = new HashMap<>();
// 初始化图,这里假定所有客户节点都在输入中,虽然不一定从 1 开始,连续增加
int[][] dist = new int[n + 1][n + 1];
for (int i = 0; i <= n; i++) {
Arrays.fill(dist[i], INF);
dist[i][i] = 0;
}
int index = 1;
// 读取投递站到客户的距离
for (int i = 0; i < n; i++) {
line = scanner.nextLine().split(" ");
int id = Integer.parseInt(line[0]);
int distance = Integer.parseInt(line[1]);
dist[0][index] = distance;
dist[index][0] = distance;
custToIndex.put(id, index);
++index;
}
// 读取客户与客户之间的距离
for (int i = 0; i < m; i++) {
line = scanner.nextLine().split(" ");
int id1 = custToIndex.get(Integer.parseInt(line[0]));
int id2 = custToIndex.get(Integer.parseInt(line[1]));
int distance = Integer.parseInt(line[2]);
dist[id1][id2] = distance;
dist[id2][id1] = distance;
}
int minDistance = getMinDistance(dist, n);
System.out.println(minDistance);
}
/**
* 计算从投递站出发,经过所有客户,返回投递站的最短路径距离
* @param dist 距离矩阵
* @param n 客户节点数量(不包括 0 号投递站节点)
* @return 最短路径距离
*/
private static int getMinDistance(int[][] dist, int n) {
// Floyd-Warshall算法求任意 2 点之间的最短路径
for (int k = 0; k <= n; k++) {
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
// 动态规划求解
// dp[i][j] 表示状态 i 在节点 j 时走过的最短路径长度
// 状态 i 表示已经访问过的节点集合,1(01)表示走过0号节点(投递站),2(10)表示走过 1 号节点(第一个客户),
// 3(11)表示走过 0 号和 1 号节点,以此类推
// n + 1个节点,全部选中的状态值为为 2 ^ (n + 1) - 1
int[][] dp = new int[1 << (n + 1)][n + 1];
for (int[] row : dp) {
Arrays.fill(row, INF);
}
// 初始状态,在第 i 个客户节点时(状态中不包括 0 号节点),走过的最短路径长度
for (int i = 1; i <= n; i++) {
dp[1 << i][i] = dist[0][i];
}
// 遍历所有状态(选中不同的客户节点组合)
for (int mask = 1; mask < (1 << (n + 1)); mask++) {
// 检查每一个节点
for (int i = 1; i <= n; i++) {
// i 节点包括在状态中
if ((mask & (1 << i)) != 0) {
// 遍历状态中其他节点,找最短路径
for (int j = 0; j <= n; j++) {
// j 节点包括在状态中,且 不等于 i 节点
if (i != j && (mask & (1 << j)) != 0) {
// 状态 mask 去掉 i 节点(异或),再加上从 j 节点到 i 节点的距离
dp[mask][i] = Math.min(dp[mask][i], dp[mask ^ (1 << i)][j] + dist[j][i]);
}
}
}
}
}
// 计算从所有客户返回投递站的最短路径距离
int minDistance = INF;
for (int i = 1; i <= n; i++) {
// 特别注意:(1 << (n + 1)) - 1 表示所有节点都选中的状态,
// 因为 0 号节点是投递站,当前状态未包括,所以状态为 (1 << (n + 1)) - 2
minDistance = Math.min(minDistance, dp[(1 << (n + 1)) - 2][i] + dist[i][0]);
}
return minDistance == INF ? -1 : minDistance;
}
}
5.2 Python实现
INF = float('inf')
def get_min_distance(dist, n):
# Floyd-Warshall算法求任意2点之间的最短路径
for k in range(n + 1):
for i in range(n + 1):
for j in range(n + 1):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
# 动态规划求解
# dp[mask][i] 表示状态 mask 在节点 i 时走过的最短路径长度
dp = [[INF] * (n + 1) for _ in range(1 << (n + 1))]
for i in range(1, n + 1):
dp[1 << i][i] = dist[0][i]
for mask in range(1, 1 << (n + 1)):
for i in range(1, n + 1):
if mask & (1 << i):
for j in range(n + 1):
if i != j and mask & (1 << j):
dp[mask][i] = min(dp[mask][i], dp[mask ^ (1 << i)][j] + dist[j][i])
# 计算从所有客户返回投递站的最短路径距离
min_distance = INF
for i in range(1, n + 1):
min_distance = min(min_distance, dp[(1 << (n + 1)) - 2][i] + dist[i][0])
return -1 if min_distance == INF else int(min_distance)
def main():
n, m = map(int, input().split())
cust_to_index = {}
dist = [[INF] * (n + 1) for _ in range(n + 1)]
for i in range(n + 1):
dist[i][i] = 0
for i in range(n):
cust_id, distance = map(int, input().split())
cust_to_index[cust_id] = i + 1
dist[0][i + 1] = distance
dist[i + 1][0] = distance
for i in range(m):
cid1, cid2, distance = map(int, input().split())
id1 = cust_to_index[cid1]
id2 = cust_to_index[cid2]
dist[id1][id2] = distance
dist[id2][id1] = distance
min_distance = get_min_distance(dist, n)
print(min_distance)
if __name__ == "__main__":
main()