小练习记录贴03
点击跳转原题
题目描述
作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。
输入格式
输入第一行给出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
解题思路
看到这题的时候我就想到用dijkstra(什么是dijkstra),最短路径问题嘛。但是题目要求除了找到最短路径花销还要找到那条救援队伍最多的线路。进而我想到用两次dijkstra,第一次先找到最短路径的值,第二次做dijkstra的时候加入比较,如果抵达D的时候花销和最短路径是一样的就进行回溯,回溯累加上每个point的救援队伍。然而这样做在pta上面只能拿到18分(满分25,有一个7分节点过不去)。
进而参考了其他coder的解题思路。%%%膜拜大佬的思路。
她在其中只用了一次dijkstra解决了问题。但是比我多增加了两个数组,一个是用来记录抵达当前位置路径数量的num数组,另外一个是记录抵达当前位置最多救援队伍数量的w数组。
前面的过程和普通dijkstra是一样的。后面更新数据分为两个情况:
- 如果当前节点A加上A到B的距离小于当前出发点到B的距离,就更新dist, num, w数组,dist数组不用说了。num[B]更新为num[A],因为现在A是更优的情况,所以抵达B的所有路线都必定要途径A,也就是说能到A的路都可以到B。w[B]更新为w[A] + team[B],原本B节点的队伍数量加上前面抵达A位置最多救援队伍数量。
- 如果当前节点A加上A到B的距离等于当前出发点到B的距离。num[B]更新为num[B] + num[A],相当于多了另外一条路能过来,而这另外一条路是必定经过A节点的,所以只用关注A的情况就可以了。w需要比较一下,如果当前这条路的队伍数量比前面路的救援数量要多,那么更新为w[A] + team[B];反之不更新。
另外需要注意的是,这是一个无向图,所以输入图的时候要更新两条边。
思路非常巧妙,值得反复学习。
AC代码
#include<iostream>
#include<vector>
using namespace std;
#define inf 0x3f3f3f3f
int mp[505][505]; //记录路径
int team[505]; //记录救援队数量
bool visited[505]; //标记是否到过了
int before[505]; //当前城市的前一个节点
int dist[505]; //S到达这个点最短的距离
int num[505]; //记录路径数量
int w[505]; //记录最大权重
void getPath(int point){
if(point == before[point]) {
cout << point;
return;
}
getPath(before[point]);
cout << " " << point;
}
int main(){
int N, M, S, D; cin >> N >> M >> S >> D;
for(int i = 0; i < N; i ++)
cin >> team[i];
for(int i = 0; i < N; i ++){
dist[i] = inf;
for(int j = 0; j < N; j ++)
mp[i][j] = inf;
}
while(M --){
int city1, city2, d; cin >> city1 >> city2 >> d;
mp[city1][city2] = min(mp[city1][city2], d);
mp[city2][city1] = min(mp[city2][city1], d);
}
dist[S] = 0; //有这个初始化就可以确保第一次dijkstrea 从S开始
w[S] = team[S];
num[S] = 1;
int cnt = 0;
while(cnt < N){
//查找当前的最短距离的点
int minDist = inf, point = -1;
for(int i = 0; i < N; i ++){
if(!visited[i] && minDist > dist[i]){
minDist = dist[i];
point = i;
}
}
if(point == -1) break;
visited[point] = true;
//更新
for(int i = 0; i < N; i ++){
if(visited[i]) continue; //当前确认的点一定是最短距离
if(dist[point] + mp[point][i] < dist[i]){//排除了mp[point][i]==inf的情况
dist[i] = dist[point] + mp[point][i];
num[i] = num[point]; //抵达i的线路必定途径point
w[i] = w[point] + team[i];
before[i] = point;
}else if(dist[point] + mp[point][i] == dist[i]){
num[i] += num[point]; //i之前没有和point连通,连通之后多了num[point]个线路
if(w[point] + team[i] > w[i]){
w[i] = team[i] + w[point];
before[i] = point;
}
}
}
cnt ++;
}
cout << num[D] << " " << w[D] << endl;
getPath(D);
return 0;
}