题目
南山的登山路中包含 N 个休息点和 M 条道路。每条道路双向连接两个不同的休息点。休息点从 1 到 N 进行编号,山顶的休息点为 1 号休息点。
澈秀喜欢登山,所以每次从一个休息点走到另一个休息点时,他都会选择经过 1 号休息点的路线。但是,澈秀会选择最短的路线,避免旅途过于艰辛。
下 [图] 的示例中有四个休息点和四条道路。圆圈中的数字是休息点的编号,道路旁边的数字是道路的长度。
澈秀从一个休息点到另一个休息点的所有情况及其走过的距离分别如下:从 ① 到 ②,走过的距离是 5;从 ① 到 ③,走过的距离是 4;从 ① 到 ④ 走过的距离是 7;从 ② 到 ③ 走过的距离是 9;从 ② 到 ④ 走过的距离是 12;从 ③ 到 ④ 走过的距离是 11。
编写一个程序,输入登山路线的结构信息,计算从休息点 i 到休息点 j (i < j) 的所有情况走过的总距离。在上文的示例中,所有的情况走过的总距离为 48 (5 + 4 + 7 + 9 + 12 + 11)。
[限制条件]
1.休息点数量 N 为介于 2 到 100,000 之间的自然数。
2.道路数量 M 为介于 N-1 到 200,000 之间的自然数。
3.每条道路的长度为介于 1 到 10,000 之间的自然数。
4.两个休息点之间的道路可能有多条。
5.没有道路可以从一个休息点出来再到同一个休息点。
6.每个休息点到另一个休息点之间的道路都有不止一条。
[输入]
在第一行,给定测试用例个数 T。然后给定 T 种测试用例情况。在每个测试用例的第一行,给定休息点数量 N 和道路数量 M,以空格分隔。接下来的 M 行,每行给定一条道路信息。道路信息采用三个数字的形式。前两个数字为道路两端的休息点编号,最后一个数字为道路长度。
[输出]
采用标准输出格式按顺序输出每个测试用例的答案。每个测试用例,都输出“#x”(x 为测试用例的编号,从 1 开始),加一个空格(不含引号),然后输出走过的总距离。
[输入和输出示例]
(输入)
2
4 4
1 2 5
3 1 4
4 2 2
4 3 3
3 2
1 2 2
3 2 3
(输出)
#1 48
#2 14
思路
每次到达下一个点必须经过1号店,可以拆解为从当前点到1号点的最短路径,加上从1号点到目的地的最短路径。
也就是说,整个过程可以分解为:
1号点到剩余所有点:【1 --> 2】+【1 --> 3】+【1 --> 4】+…+【1 --> N】
2号点到剩余所有点:【2 --> 1 --> 3】+【2 --> 1 --> 4】+【2 --> 1 --> 5】+…+【2 --> 1 --> N】
3号点到剩余所有点:【3 --> 1 --> 4】+【3 --> 1 --> 5】+…+【3 --> 1 --> N】
4号点到剩余所有点:【4 --> 1 --> 5】+…+【4 --> 1 --> N】
…
N-1号点到剩余所有点:【N-1 --> 1 --> N】
可以观察出:
过程中均为1号点和其它点的路程
且:
【1 --> 2】或【2 --> 1】最短路径使用次数:N - 1
【1 --> 3】或【3 --> 1】最短路径使用次数:N - 1
…
【N --> 1】或【N --> 1】最短路径使用次数:N - 1
则:
- 使用Dijkstra,求出1号点到所有点的最短路径COST(无向图,也就是所有点到1号点的最短路径);
- 遍历1号点到所有点的最短路径,对于i点,ASW += (i - 1) * COST[i];
- ASW即为所求。
代码
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;
public class Main {
static int T, N, M;
static ArrayList<Edg>[] DATA; // 原始数据
static long COST[], ASW;
public static void main(String[] args) throws Exception {
System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210903.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int T = Integer.parseInt(br.readLine());
for (int tc = 1; tc <= T; tc++) {
st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
COST = new long[N + 1];
Arrays.fill(COST, 30_0000_0000L);
ASW = 0L;
DATA = new ArrayList[N + 1];
for (int i = 1; i <= N; i++)
DATA[i] = new ArrayList<>();
// 邻接链表读入边、权值数据并存储
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int from = Integer.parseInt(st.nextToken());
int to = Integer.parseInt(st.nextToken());
long cost = Long.parseLong(st.nextToken());
DATA[from].add(new Edg(to, cost));
DATA[to].add(new Edg(from, cost));
}
// 迪杰斯特拉求最短路径
dijkstra();
for (int i = 2; i <= N; i++)
ASW += (N - 1) * COST[i];
System.out.printf("#%d %d\n", tc, ASW);
}
}
static void dijkstra() {
boolean visited[] = new boolean[N + 1]; // 已求得最短路径的点标记为true
int visitedCount = 0; // 已求得最短路径的点个数
// 按当前到达点的成本升序排序
PriorityQueue<Edg> pq = new PriorityQueue<>(new Comparator<Edg>() {
@Override
public int compare(Edg o1, Edg o2) {
return Long.compare(o1.cost, o2.cost);
}
});
pq.add(new Edg(1, 0L)); // 设定起点为1号点
while (!pq.isEmpty()) {
Edg now = pq.poll(); // 获取当前到达的点
if (visited[now.point]) // 当前点的最短路径已求得
continue;
// 更新当前点的最短路径,并标记
visited[now.point] = true;
COST[now.point] = now.cost;
if (++visitedCount == N) // 所有点的最短路径已求得
break;
// 遍历当前点的连接点
for (Edg next : DATA[now.point]) {
if (visited[next.point]) // 若下一个节点的最短路径已经确定
continue;
// 则松弛操作
if (COST[next.point] > now.cost + next.cost) {
COST[next.point] = now.cost + next.cost;
pq.add(new Edg(next.point, now.cost + next.cost));
}
}
}
}
static class Edg {
int point;
long cost;
public Edg(int p, long cost) {
this.point = p;
this.cost = cost;
}
}
}