[KamaCoder] 96. 城市间货物运输 III 文章解释
题目描述
某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。
网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。
请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。
输入描述
第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。
接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。
最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。
输出描述
输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 "unreachable",表示不存在符合条件的运输方案。
输入示例
6 7 1 2 1 2 4 -3 2 5 2 1 3 5 3 5 1 4 6 4 5 6 -2 2 6 1
输出示例
0
提示信息
从 2 -> 5 -> 6 中转一站,运输成本为 0。
1 <= n <= 1000;
1 <= m <= 10000;
-100 <= v <= 100;
[KamaCoder] 96. 城市间货物运输 III (Bellman_Ford 单源限制节点最短路径算法)
自己看到题目的第一想法
无
看完代码随想录之后的想法
嗯... 很复杂... 复杂到脑子不知道怎么计算了... 不过比起 Floyd 还是直观一些.
核心的思想就是, 如果每次只展开和出发站点最近的边, 那么经过展开 k + 1 条边(k 为允许经过的车站数量), 就能计算出从出发站点到目的站点的最短距离. 我个人认为, 目前的算法是允许在负权回路里转圈圈的.
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.Queue;
import java.util.Scanner;
public class Main {
private static LinkedList<Edge>[] nodes = null;
private static int[] minDist = null;
private static int[] minDistCopy = null;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nodeCount = 0;
int sideCount = 0;
nodeCount = scanner.nextInt();
sideCount = scanner.nextInt();
minDist = new int[nodeCount + 1];
minDistCopy = new int[nodeCount + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
nodes = new LinkedList[nodeCount + 1];
for (int i = 1; i <= nodeCount; i++) {
nodes[i] = new LinkedList();
}
for (int i = 0; i < sideCount; i++) {
Edge edge = new Edge(scanner.nextInt(),
scanner.nextInt(), scanner.nextInt());
nodes[edge.from].add(edge);
}
int start = scanner.nextInt();
int end = scanner.nextInt();
int limit = scanner.nextInt();
minDist[start] = 0;
// 松弛 limit + 1 次
for (int i = 0; i <= limit; i++) {
System.arraycopy(minDist, 0, minDistCopy, 0, minDistCopy.length);
for (int j = 1; j < nodes.length; j++) {
for (Edge edge : nodes[j]) {
if (minDistCopy[edge.from] != Integer.MAX_VALUE
&& minDistCopy[edge.from] + edge.value < minDist[edge.to]) {
minDist[edge.to] = minDistCopy[edge.from] + edge.value;
}
}
}
}
if (minDist[end] == Integer.MAX_VALUE) {
System.out.println("unreachable");
} else {
System.out.println(minDist[end]);
}
}
private static class Edge {
int from;
int to;
int value;
public Edge(int from, int to, int value) {
this.from = from;
this.to = to;
this.value = value;
}
}
}
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.Queue;
import java.util.Scanner;
// Floyd 算法
public class Main {
private static LinkedList<Edge>[] nodes = null;
private static int[] minDist = null;
private static int[] minDistCopy = null;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nodeCount = 0;
int sideCount = 0;
nodeCount = scanner.nextInt();
sideCount = scanner.nextInt();
minDist = new int[nodeCount + 1];
minDistCopy = new int[nodeCount + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
nodes = new LinkedList[nodeCount + 1];
for (int i = 1; i <= nodeCount; i++) {
nodes[i] = new LinkedList();
}
for (int i = 0; i < sideCount; i++) {
Edge edge = new Edge(scanner.nextInt(),
scanner.nextInt(), scanner.nextInt());
nodes[edge.from].add(edge);
}
int start = scanner.nextInt();
int end = scanner.nextInt();
int limit = scanner.nextInt();
minDist[start] = 0;
Queue<Integer> queue = new LinkedList<>();
queue.offer(start);
// 松弛 limit + 1 次
while (limit-- >= 0 && !queue.isEmpty()) {
System.arraycopy(minDist, 0, minDistCopy, 0, minDistCopy.length);
boolean visited[] = new boolean[nodeCount + 1];
int queueSize = queue.size();
// 松弛所有与已经松弛过的节点连接的边(节点)
while (queueSize-- > 0) {
int currentNode = queue.poll();
for (Edge edge : nodes[currentNode]) {
if (minDistCopy[edge.from] != Integer.MAX_VALUE
&& minDistCopy[edge.from] + edge.value < minDist[edge.to]) {
minDist[edge.to] = minDistCopy[edge.from] + edge.value;
if (visited[edge.to]) {
continue;
}
queue.offer(edge.to);
visited[edge.to] = true;
}
}
}
}
if (minDist[end] == Integer.MAX_VALUE) {
System.out.println("unreachable");
} else {
System.out.println(minDist[end]);
}
}
private static class Edge {
int from;
int to;
int value;
public Edge(int from, int to, int value) {
this.from = from;
this.to = to;
this.value = value;
}
}
}
自己实现过程中遇到哪些困难
无... 因为感觉自己是在背代码.
题目描述
小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。
给定一个公园景点图,图中有 N 个景点(编号为 1 到 N),以及 M 条双向道路连接着这些景点。每条道路上行走的距离都是已知的。
小明有 Q 个观景计划,每个计划都有一个起点 start 和一个终点 end,表示他想从景点 start 前往景点 end。由于小明希望节省体力,他想知道每个观景计划中从起点到终点的最短路径长度。 请你帮助小明计算出每个观景计划的最短路径长度。
输入描述
第一行包含两个整数 N, M, 分别表示景点的数量和道路的数量。
接下来的 M 行,每行包含三个整数 u, v, w,表示景点 u 和景点 v 之间有一条长度为 w 的双向道路。
接下里的一行包含一个整数 Q,表示观景计划的数量。
接下来的 Q 行,每行包含两个整数 start, end,表示一个观景计划的起点和终点。
输出描述
对于每个观景计划,输出一行表示从起点到终点的最短路径长度。如果两个景点之间不存在路径,则输出 -1。
输入示例
7 3 2 3 4 3 6 6 4 7 8 2 2 3 3 4
输出示例
4 -1
提示信息
从 2 到 3 的路径长度为 4,3 到 4 之间并没有道路。
1 <= N, M, Q <= 1000.
[KamaCoder] 97. 小明逛公园
自己看到题目的第一想法
无
看完代码随想录之后的想法
动态规划来了!!! 抄了那么多动态规划的代码, 我对动态规划依旧一无所知. 我真的适合写程序吗, 我有理解算法的能力吗? 除了坚持和重复, 没有别的想法了.
本质上就是, 对于每一个路径, 都计算一下 从 i -> k -> j 的路径, 是否比直接从 i -> j 更近. 对于 grid[i][j][k] 有两种途径可以推导, 一个是 grid[i][j][k - 1], 一个是 grid[i][k][k - 1] + grid[k][j][k - 1]. grid[i][j][k - 1] 就是说, 我不从节点 k 走了, 直接依赖上一层推算出来的值. grid[i][k][k - 1] + grid[k][j][k - 1] 就是说, 我要去节点 k 转一下, 看看 i -> k -> j 这样的路径, 值是多少. grid[i][j][1] 就是 i -> j 的路径的权值. grid[i][j][2] 就是 i -> j 和 i -> 1 -> j 这两个路径选择更近的, 同理 grid[i][j][3] 就是 i -> j、i -> 1 ->j、i -> 2 -> j 三者中最近的那个. 动态规划的核心在这里了.
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.Queue;
import java.util.Scanner;
public class Main {
private static List<List<Edge>> nodes = new ArrayList<>();
private static List<Edge> queryNodes = new ArrayList<>();
private static int[][][] grid = null;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nodeCount = 0;
int sideCount = 0;
nodeCount = scanner.nextInt();
sideCount = scanner.nextInt();
grid = new int[nodeCount + 1][nodeCount + 1][nodeCount + 1];
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
for (int k = 0; k < grid[i][j].length; k++) {
grid[i][j][k] = Integer.MAX_VALUE/2;
}
}
}
int from;
int to;
int value;
for (int i = 0; i < sideCount; i++) {
from = scanner.nextInt();
to = scanner.nextInt();
value = scanner.nextInt();
grid[from][to][0] = value;
grid[to][from][0] = value;// 这里是双向图
}
int queryCount = scanner.nextInt();
for (int i = 0; i < queryCount; i++) {
queryNodes.add(new Edge(scanner.nextInt(), scanner.nextInt(), 0));
}
for (int k = 1; k <= nodeCount; k++) {
for (int i = 1; i <= nodeCount; i++) {
for (int j = 1; j <= nodeCount; j++) {
grid[i][j][k] = Math.min(grid[i][j][k - 1],
grid[i][k][k - 1] + grid[k][j][k - 1]);
}
}
}
for (Edge edge : queryNodes) {
if (grid[edge.from][edge.to][nodeCount] == Integer.MAX_VALUE/2) {
System.out.println(-1);
} else {
System.out.println(grid[edge.from][edge.to][nodeCount]);
}
}
}
private static class Edge {
int from;
int to;
int value;
public Edge(int from, int to, int value) {
this.from = from;
this.to = to;
this.value = value;
}
}
}
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.Queue;
import java.util.Scanner;
public class Main {
private static List<List<Edge>> nodes = new ArrayList<>();
private static List<Edge> queryNodes = new ArrayList<>();
private static int[][] grid = null;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nodeCount = 0;
int sideCount = 0;
nodeCount = scanner.nextInt();
sideCount = scanner.nextInt();
grid = new int[nodeCount + 1][nodeCount + 1];
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
grid[i][j] = Integer.MAX_VALUE/2;
}
}
int from;
int to;
int value;
for (int i = 0; i < sideCount; i++) {
from = scanner.nextInt();
to = scanner.nextInt();
value = scanner.nextInt();
grid[from][to] = value;
grid[to][from] = value;// 这里是双向图
}
int queryCount = scanner.nextInt();
for (int i = 0; i < queryCount; i++) {
queryNodes.add(new Edge(scanner.nextInt(), scanner.nextInt(), 0));
}
for (int k = 1; k <= nodeCount; k++) {
for (int i = 1; i <= nodeCount; i++) {
for (int j = 1; j <= nodeCount; j++) {
grid[i][j] = Math.min(grid[i][j], grid[i][k] + grid[k][j]);
}
}
}
for (Edge edge : queryNodes) {
if (grid[edge.from][edge.to] == Integer.MAX_VALUE/2) {
System.out.println(-1);
} else {
System.out.println(grid[edge.from][edge.to]);
}
}
}
private static class Edge {
int from;
int to;
int value;
public Edge(int from, int to, int value) {
this.from = from;
this.to = to;
this.value = value;
}
}
}
自己实现过程中遇到哪些困难
生成图的时候, 没有注意到是双向图, 只生成了单向图.