题目大意:
作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。
输入格式:
输入第一行给出4个正整数N、M、S、D,其中N(2<=N<=500)是城市的个数,顺便假设城市的编号为0~(N-1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。
输出格式:
第一行输出不同的最短路径的条数和能够召集的最多的救援队数量。第二行输出从S到D的路径中经过的城市编号。
数字间以空格分隔,输出首尾不能有多余空格。
输入样例:
4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2
输出样例:
2 60
0 1 3
思路:在迪杰斯特拉的基础之上运用DP来解决问题。传统的迪杰斯特拉算法就是通过不断寻找离起点最近的点,并且通过这个点来更新其余的与之相连接并且没有被选择过的点与起点之间的距离,由此来得到起点到其余各点之间的单源最短路径。而本题要求不仅要得到最短路径的长度还要得到最短路径的条数以及最大救援队数目,以及路径经过的城市。我们现在需要在更新到达i点的最小路径同时来更新所需要的信息。假设从i点到j点,当d[j]和d[i]+cost[i][j]比较之后,此时会出现三种情况,(1)d[j]<d[i]+cost[i][j],那么此时不做任何处理,因为对最短路径没有任何影响,(2)d[j]==d[i]+cost[i][j],说明通过i点找到了新的到达j点的最短路径,此时到达j点的路径总数要加上到达i点最短路的路径总数,之后比较到达i点时的最大救援队的数量加上j点本身队伍数量之和以及j点原来所计算出的最大救援队数目之和,若total[j]<total[i]+team[i],那么此时还需对j点的救援队数目进行更新,由于还要找到路径经过的城市,我们为每一个节点记载其前驱节点,随着对救援队数目的更改,说明在各条最短路径之中所选择的路径发生了改变,那么我们也要将pre[j]改变为i节点。(3)d[j]>d[i]+cost[i][j],说明过i点到达j点的路径才是最短路径,那么此时不光要对j点前驱节点的记载,最短路径,以及救援队数目进行更新外,最重要的是该点的最短路径条数也要相应更新为i点最短路径的长度,而不是加上。JAVA版本最后四个测试用例超时,改为C/C++版本可以通过测试。
public class Main {
//城市个数
public static int N;
//路径条数
public static int M;
//起点城市的坐标
public static int S;
//目标城市的坐标
public static int D;
//每个节点拥有的队伍数量
public static int team[] = new int[N];
//每个节点的前一个节点下标
public static int pre[] = new int[N];
//到i节点共有几条路
public static int ways[] = new int[N];
//到i节点最多有几支队伍
public static int total[] = new int[N];
//每个节点是否被遍历过
public static boolean used[] = new boolean[N];
//从起点到i城市的距离
public static int d[] = new int[N];
//每条路的长度
public static int cost[][] = new int[N][N];
//变形的迪杰斯特拉算法
public static void solve(int sIndex,int dIndex)
{
for(int i=0;i<N;i++)
{
d[i] = Integer.MAX_VALUE;
pre[i] = -1;
total[i] = team[i];
used[i] = false;
}
d[sIndex] = 0;
ways[sIndex] = 1;
while(true)
{
int flag = -1;
for(int i=0;i<N;i++)
{
if(!used[i]&&(flag==-1||d[i]<d[flag]))
flag = i;
}
if(flag==dIndex)
break;
used[flag] = true;
for(int i=0;i<N;i++)
{
if((d[flag]+cost[flag][i]<d[i]))
{
pre[i] = flag;
d[i] = d[flag]+cost[flag][i];
ways[i] = ways[flag];
total[i] = team[i]+total[flag];
}
else
{
if((d[flag]+cost[flag][i]==d[i]))
{
ways[i] += ways[flag];
if(total[i]<total[flag]+team[i])
{
pre[i] = flag;
total[i] = total[flag]+team[i];
}
}
}
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
M = sc.nextInt();
S = sc.nextInt();
D = sc.nextInt();
team = new int[N];
pre = new int[N];
ways = new int[N];
total = new int[N];
used = new boolean[N];
d = new int[N];
cost = new int[N][N];
//输入每个节点所拥有的救援队数量
for(int i=0;i<N;i++)
{
team[i] = sc.nextInt();
}
//初始化cost数组
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
cost[i][j] = 0x3f3f3f3f;
//输入路径长度
for(int a=0;a<M;a++)
{
int i = sc.nextInt();
int j = sc.nextInt();
int value = sc.nextInt();
cost[i][j] = cost[j][i] = value;
}
solve(S,D);
System.out.print(ways[D]+" "+total[D]);
System.out.print("\n");
ArrayList<Integer> list = new ArrayList<Integer>();
int index = D;
//逆向回推并保存所经过的路径
while(index!=-1)
{
list.add(pre[index]);
index = pre[index];
}
//正向输出所经过的路径
for(int i=list.size()-2;i>=0;i--)
{
System.out.print(list.get(i)+" ");
}
System.out.print(D);
}
}