[KamaCoder] 94. 城市间货物运输 I 文章解释
题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。
城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v (单向图)。
输出描述
如果能够从城市 1 到连通到城市 n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市 1 没有路径可达城市 n,请输出 "unconnected"。
输入示例
6 7 5 6 -2 1 2 1 5 3 1 2 5 2 2 4 -3 4 6 4 1 3 5
输出示例
1
示例中最佳路径是从 1 -> 2 -> 5 -> 6,路上的权值分别为 1 2 -2,最终的最低运输成本为 1 + 2 + (-2) = 1。
示例 2:
4 2
1 2 -1
3 4 -1在此示例中,无法找到一条路径从 1 通往 4,所以此时应该输出 "unconnected"。
数据范围:
1 <= n <= 1000;
1 <= m <= 10000;-100 <= v <= 100;
[KamaCoder] 94. 城市间货物运输 I (SPFA )
自己看到题目的第一想法
无
看完代码随想录之后的想法
之前的 Dijkstra 算法, 需要路径权值非负数, 因为一旦访问过的节点, 就不会再访问了. 对于 Dijkstra 算法, 如果一个节点距离原点路径权值的和为 N, 且当前的 N 是所有可触达的节点中最小的, 那么从其他节点再触达当前节点, 路径权值的和一定是小于等于 N 的. 因此在 Dijkstra 中, 一个节点不能多次访问.
然而当出现负权值的时候, 一个节点是需要多次访问的. 而 Bellman_Ford 算法就是不停的计算节点的值, 一共计算 N - 1 次, N 为节点的数量. 因为在一个有 N 个节点的图中, 一个节点最多和 N - 1 个节点关联, 因此通过 N - 1 次松弛, 就可以计算出所有节点到原始节点的最小距离.
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;
public class Main {
private static Map<Integer, List<Edge>> nodes = new HashMap<>();
private static int[] minDist = null;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nodeCount = 0;
while (scanner.hasNext()) {
nodeCount = scanner.nextInt();
int sideCount = scanner.nextInt();
minDist = new int[nodeCount + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
int from;
int to;
int value;
for (int i = 0; i < sideCount; i++) {
Edge edge = new Edge();
edge.from = scanner.nextInt();
edge.to = scanner.nextInt();
edge.value = scanner.nextInt();
List<Edge> edges = nodes.get(edge.from);
if (edges == null) {
edges = new ArrayList<>();
nodes.put(edge.from, edges);
}
edges.add(edge);
}
}
minDist[1] = 0;
Deque<Integer> dealedNodes = new LinkedList<>();
dealedNodes.add(1);
while (!dealedNodes.isEmpty()) {
int node = dealedNodes.pop();
List<Edge> toEdges = nodes.get(node);
if (toEdges == null) {
continue;
}
for (Edge edge : toEdges) {
if (minDist[node] + edge.value < minDist[edge.to]) {
minDist[edge.to] = minDist[node] + edge.value;
if (!dealedNodes.contains(edge.to)) {
dealedNodes.addLast(edge.to);
}
}
}
}
if (minDist[nodeCount] == Integer.MAX_VALUE) {
System.out.println("unconnected");
} else {
System.out.println(minDist[nodeCount]);
}
}
private static class Edge {
int from;
int to;
int value;
}
}
自己实现过程中遇到哪些困难
无
[KamaCoder] 95. 城市间货物运输 II 文章解释
题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。
请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。
城市 1 到城市 n 之间可能会出现没有路径的情况
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
输出描述
如果没有发现负权回路,则输出一个整数,表示从城市
1
到城市n
的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 "circle"。如果从城市 1 无法到达城市 n,则输出 "unconnected"。输入示例
4 4 1 2 -1 2 3 1 3 1 -1 3 4 1
输出示例
circle
提示信息
路径中存在负权回路,从 1 -> 2 -> 3 -> 1,总权值为 -1,理论上货物运输商可以在该回路无限循环赚取政府补贴,所以输出 "circle" 表示已经检测出了该种情况。
数据范围:
1 <= n <= 1000;
1 <= m <= 10000;-100 <= v <= 100;
[KamaCoder] 95. 城市间货物运输 II
自己看到题目的第一想法
无
看完代码随想录之后的想法
在一个无负权回路的图中, 如果一共有 N 个节点, 那么经过 N - 1 次松弛, 则 minDist 数组一定就稳定了. 而在有负权回路的图中, 在第 N 次松弛的时候, 就会出现 minDist 数组需要更新的情况. 因此只要松弛 N 次, 如果第 N 次出现 minDist 需要被更新的情况, 就是出现了负权回路.
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;
public class Main {
private static Map<Integer, List<Edge>> nodes = new HashMap<>();
private static int[] minDist = null;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nodeCount = 0;
while (scanner.hasNext()) {
nodeCount = scanner.nextInt();
int sideCount = scanner.nextInt();
minDist = new int[nodeCount + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
for (int i = 0; i < sideCount; i++) {
Edge edge = new Edge();
edge.from = scanner.nextInt();
edge.to = scanner.nextInt();
edge.value = scanner.nextInt();
List<Edge> edges = nodes.get(edge.from);
if (edges == null) {
edges = new ArrayList();
nodes.put(edge.from, edges);
}
edges.add(edge);
}
}
minDist[1] = 0;
boolean flag = false;
for (int i = 1; i <= nodeCount; i++) {
for (Map.Entry<Integer, List<Edge>> entry : nodes.entrySet()) {
List<Edge> edges = entry.getValue();
if (edges == null) {
continue;
}
for (Edge edge : edges) {
if (minDist[edge.from] == Integer.MAX_VALUE
|| minDist[edge.from] + edge.value >= minDist[edge.to]) {
continue;
}
if (i < nodeCount) {
minDist[edge.to] = minDist[edge.from] + edge.value;
} else {
flag = true;
break;
}
}
}
}
if (flag) {
System.out.println("circle");
} else if (minDist[nodeCount] == Integer.MAX_VALUE) {
System.out.println("unconnected");
} else {
System.out.println(minDist[nodeCount]);
}
}
private static class Edge {
int from;
int to;
int value;
}
}
自己实现过程中遇到哪些困难
重点是思路, 实现上就是在松弛所有边的时候判断当前是第几次松弛, 如果是第 N 次(N 为节点个数), 且出现 minDist 需要更新, 那就是出现了负权回路.