目录
题目描述
原题来自:USACO 2006 Nov. Gold
贝茜把家搬到了一个小农场,但她常常回到 FJ 的农场去拜访她的朋友。贝茜很喜欢路边的风景,不想那么快地结束她的旅途,于是她每次回农场,都会选择第二短的路径,而不象我们所习惯的那样,选择最短路。
贝茜所在的乡村有 R(1≤R≤105) 条双向道路,每条路都连接了所有的 N(1≤N≤5000) 个农场中的某两个。贝茜居住在农场 1,她的朋友们居住在农场 N(即贝茜每次旅行的目的地)。
贝茜选择的第二短的路径中,可以包含任何一条在最短路中出现的道路,并且一条路可以重复走多次。当然第二短路的长度必须严格大于最短路(可能有多条)的长度,但它的长度必须不大于所有除最短路外的路径的长度。
输入格式
第 1 行为两个整数,N 和 R,用空格隔开;
第 2…R+1 行:每行包含三个用空格隔开的整数 A、B 和 D,表示存在一条长度为 D(1≤D≤5000) 的路连接农场 A 和农场 B。
输出格式
输出仅一个整数,表示从农场 1 到农场 N 的第二短路的长度。
样例输入
4 4
1 2 100
2 4 200
2 3 250
3 4 100
样例输出
450
提示
最短路:1→2→4(长度为 100+200=300)
第二短路:1→2→3→4(长度为 100+250+100=450)
原题链接
代码思路
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// 键盘录入.
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int R = scanner.nextInt();
// 存储点和权值的集合.
ArrayList<lx1_2> list[] = new ArrayList[N];
for (int i = 0; i < N; i++) {
// 初始化集合list[i],不然后面会报错.
list[i] = new ArrayList<lx1_2>();
}
for (int i = 0; i < R; i++) {
// 将数据存储到集合list中,方便计算.
addlx1_2(list, scanner.nextInt() - 1, scanner.nextInt() - 1, scanner.nextInt());
}
// 输出次最小路径.
System.out.println(SubminimalSpanningTree(list, N));
}
// 存储点和边的权值.
static void addlx1_2(ArrayList<lx1_2> list[], int id1, int id2, int weight) {
list[id1].add(new lx1_2(id2, weight));
list[id2].add(new lx1_2(id1, weight));
}
// 生成严格次最小路径.
static int SubminimalSpanningTree(ArrayList<lx1_2> list[], int n) {
// 赋一个大的值(因为找的是小值).
int INF = Integer.MAX_VALUE;
// 最小路径存储(通过while最后得到最优的最小路径).
int MinimumPath[] = new int[n];
// 次最小路径存储(通过while最后得到最优的次最小路径).
int Secondaryminimumpath[] = new int[n];
// 为数组MinimumPath和数组Secondaryminimumpath,初始化为INF.
Arrays.fill(MinimumPath, INF);
Arrays.fill(Secondaryminimumpath, INF);
// 利用PriorityQueue(优先队列)实现由从小到大的权值排列的集合,从而得出最优解.
PriorityQueue<lx1_2> queue = new PriorityQueue<lx1_2>();
// 加入队列第一个元素.
queue.add(new lx1_2(0, 0));
// 当优先队列为空的时,即得出最优解循环结束.
while (!queue.isEmpty()) {
// poll()函数取出队列中第一个元素并删除.
lx1_2 qLx1_2 = queue.poll();
// 遍历出和qLx1_2.id连接的点,寻找最优解.
for (lx1_2 lx : list[qLx1_2.id]) {
// qLx1_2.weight是之前边权值的总和,lx.weight是qLx1_2.id连接的点的边权值.
int Weightsand = lx.weight + qLx1_2.weight;
// Weightsand与MinimumPath[lx.id]相比较,如果小则赋值给MinimumPath[lx.id],最后的结果是MinimumPath[lx.id]成为到lx.id这个点的最小总权值.
if (Weightsand < MinimumPath[lx.id]) {
// 这个导致Secondaryminimumpath[lx.id]永远比MinimumPath[lx.id]慢一步,会让严格次最小路径得出.
Secondaryminimumpath[lx.id] = MinimumPath[lx.id];
// MinimumPath[lx.id]获得新的最优值.
MinimumPath[lx.id] = Weightsand;
// 将新的最优值放入队列,让数组Secondaryminimumpath逐渐优化,使严格次最小路径得出.
queue.add(new lx1_2(lx.id, Weightsand));
// 有可能权值和大于最小权值和而小于次小权值和,所以要更新集合Secondaryminimumpath.
} else if (Weightsand > MinimumPath[lx.id] && Weightsand < Secondaryminimumpath[lx.id]) {
Secondaryminimumpath[lx.id] = Weightsand;
// 将新的最优数放入队列,让严格次最小路径得出.(因为怕这条路上有更优的,所以一定要加这一步).
queue.add(new lx1_2(lx.id, Weightsand));
}
}
}
// 返回最后到达点的次最小权值.
return Secondaryminimumpath[n - 1];
}
}
//类lx1_2是用来存储键盘录入数据的,是辅助list[i] = new ArrayList<lx1_2>();来存储数据的.
//如果键盘录入 1 2 3 ,那么类lx1_2用来存储2和3.
class lx1_2 implements Comparable<lx1_2> {
int id;
int weight;
public lx1_2(int id, int weight) {
super();
this.id = id;
this.weight = weight;
}
// compareTo(Node p)是Comparable<Node>接口的实现方法,用来规定PriorityQueue(优先队列)的排列顺序
@Override
public int compareTo(lx1_2 o) {
// TODO 自动生成的方法存根
// 这个是从小到大的排序方式.
return Integer.compare(weight, o.weight);
}
}