我的PAT-ADVANCED代码仓:https://github.com/617076674/PAT-ADVANCED
原题链接:https://pintia.cn/problem-sets/994805342720868352/problems/994805523835109376
题目描述:
题目翻译:
1003 急救
作为一名城市的急救队队长,提供给你一份你所在地区的特殊地图。此地图显示了一些分散的城市被一些道路所连接。每个城市的急救队总量和两个城市间每条道路的长度都在地图上被标示了出来。当有一个来自其他城市的急救电话时,你的任务是带领你的人尽快赶到那个城市,同时带上尽可能多的人。
输入格式:
每个输入文件包含一个测试用例。对每个测试用例,第一行包含了4个正整数:N(<= 500),城市的数目(城市编号从0 ~ N - 1);M,道路数目;C1和C2,分别代表你现在所在的城市和你需要去急救的城市。下一行包含N个正整数,第i个正整数代表第i个城市所拥有的急救队的数量。接下来的M行,每一行都用3个参数描述了一条道路信息,c1、c2和L,分别代表该道路连接的两个城市和该道路的长度。题目保证从C1到C2至少有一条道路。
输出格式:
对每个测试用例,在一行中打印两个数字:C1和C2间不同的最短路径的数量,以及你可以搜集到的最大的急救队数量。一行中的所有数字都必须被一个空格分隔,行末不允许有多余的空格。
输入样例:
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
输出样例:
2 4
知识点:Dijkstra算法、深度优先遍历、Bellman-Ford算法、SPFA算法
思路一:Dijkstra算法(邻接矩阵实现)
本题在求解最短距离的同时需要求解另外两个信息:最短路径条数和最短路径上的最大点权之和。令weight[i]表示从起点s到达顶点i可以得到的最大点权之和,初始时weight[s]有值,即起点城市的救援队数量,其余均为0。令num[i]表示从起点s到达顶点i的最短路径条数,初始化时num[s]为1,其余均为0。接下来只需要在更新d[j]的时同时更新着两个数组即可。
时间复杂度和空间复杂度均是O(N ^ 2)。
C++代码:
#include<iostream>
#include<queue>
using namespace std;
int n, m, c1, c2, INF = 1000000000; //城市数量,道路数量,起点,终点,无穷大数
int graph[501][501]; //无向图
int teams[501]; //急救队数量
int d[501]; //d[i]表示从起点c1到达点i的最短路径长度
int num[501]; //num[i]表示从起点c1到达点i的最短路径条数
int weight[501]; //weight[i]表示从起点c1到达点i所能搜集到的最大急救队数量
bool visited[501]; //dijkstra算法的标记数组,用以标记该位置是否已被访问
void dijkstra(int s);
int main(){
scanf("%d %d %d %d", &n, &m, &c1, &c2); //读取城市数量,道路数量,起点,终点
fill(visited, visited + 501, false); //初始化该标记数组全部为false,所有节点均未被访问
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
graph[i][j] = INF; //初始化图,任意两点的距离都为无穷大,即图中不存在任何路径
}
}
for(int i = 0; i < n; i++){
scanf("%d", &teams[i]); //读取每个城市急救队数量
}
int city1, city2, len;
for(int i = 0; i < m; i++){
scanf("%d %d %d", &city1, &city2, &len);
graph[city1][city2] = graph[city2][city1] = len; //读取城市间的路径信息
}
dijkstra(c1); //以c1为起点利用dijkstra算法求到其余点的最短距离
printf("%d %d\n", num[c2], weight[c2]); //输出结果
return 0;
}
void dijkstra(int s){
fill(d, d + 501, INF); //初始化源点s到其余点的最短距离均为无穷大
fill(num, num + 501, 0); //初始化起点s到达其余点的最短路径条数均为0条
fill(weight, weight + 501, 0); //初始化从起点s到达其余点所能搜集到的最大急救队数量均为0
weight[s] = teams[s]; //起点s到达其自身所能搜集到的急救队数量就是其自身拥有的急救队数量
num[s] = 1; //起点s到达其自身的最短路径条数为1
d[s] = 0; //起点s到达自身的距离为0
for(int i = 0; i < n; i++){
int u = -1, min = INF;
for(int j = 0; j < n; j++){ //寻找未访问的顶点中d[]最小的
if(!visited[j] && d[j] < min){
u = j;
min = d[j];
}
}
if(u == -1){ //找不到小于INF的d[u],说明剩下的顶点和起点不连通
return;
}
visited[u] = true; //将该最小的d[u]的u点标记为已被访问过
for(int j = 0; j < n; j++){
if(!visited[j] && graph[u][j] != INF){ //如果j未被访问过且u与j之间有路径
if(d[u] + graph[u][j] < d[j]){ //如果通过u点到达j比直接到达j要更小
d[j] = d[u] + graph[u][j]; //优化d[v]
weight[j] = weight[u] + teams[j]; //到达j点救援队的数量等于到达u点救援队的数量加上j点自身救援队的数量
num[j] = num[u]; //到达j点的最短路径条数等于到达u点的最短路径条数
}else if(d[u] + graph[u][j] == d[j]){ //如果通过u点到达j与直接到达j点的路径相等
if(weight[u] + teams[j] > weight[j]){ //如果通过u点到达j点所能搜集到的急救队数量更多
weight[j] = weight[u] + teams[j]; //更新到达j点的最大急救队数量
}
num[j] += num[u]; //到达j点的最短路径条数需要加上到达u点的最短路径条数
}
}
}
}
}
C++解题报告:
思路二:Dijkstra算法+深度优先遍历(邻接矩阵实现)
先在Dijkstra算法中记录下所有最短的路径(只考虑距离),然后从这些最短路径中选出一条第二标尺最优的路径。
时间复杂度和空间复杂度均是O(N ^ 2)。
C++代码:
#include<iostream>
#include<vector>
using namespace std;
int n, m, c1, c2, INF = 1000000000; //城市数量,道路数量,起点,终点,无穷大数
int graph[501][501]; //无向图
int teams[501]; //每个城市的急救队数量
int d[501]; //d[i]表示从起点c1到达城市i的最短路径长度
vector<int> pre[501]; //存储前一节点列表
bool visited[501]; //dijkstra算法的标记数组,用以标记该位置是否已被访问
vector<int> path; //存储能搜集到最多急救队数量的最短路径
vector<int> tempPath; //存储深度优先遍历过程中的当前路径
int optValue = 0; //能搜集到的最多急救队数量,初始化为0
int count = 0; //最短路径条数,初始化为0
void dijkstra(int s);
void dfs(int nowVisit);
int main() {
scanf("%d %d %d %d", &n, &m, &c1, &c2); //读取城市数量,道路数量,起点,终点
fill(visited, visited + 501, false); //初始化该标记数组全部为false,所有节点均未被访问
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
graph[i][j] = INF; //初始化图,任意两点的距离都为无穷大,即图中不存在任何路径
}
}
for(int i = 0; i < n; i++) {
scanf("%d", &teams[i]); //读取每个城市急救队数量
}
int city1, city2, len;
for(int i = 0; i < m; i++) {
scanf("%d %d %d", &city1, &city2, &len);
graph[city1][city2] = graph[city2][city1] = len; //读取城市间的路径信息
}
dijkstra(c1); 以c1为起点利用dijkstra算法求到其余点的最短距离
dfs(c2); //从终点开始根据记录的前一节点列表pre寻找出能搜集到的最多急救队数量以及最短路径条数
printf("%d %d\n", count, optValue); //输出结果
return 0;
}
void dijkstra(int s) {
fill(d, d + 501, INF); //初始化源点s到其余点的最短距离均为无穷大
d[s] = 0; //起点s到达自身的距离为0
for(int i = 0; i < n; i++) {
int u = -1, min = INF;
for(int j = 0; j < n; j++) { //寻找未访问的顶点中d[]最小的
if(!visited[j] && d[j] < min) {
min = d[j];
u = j;
}
}
if(u == -1) { //找不到小于INF的d[u],说明剩下的顶点和起点不连通
return;
}
visited[u] = true; 将该最小的d[u]的u点标记为已被访问过
for(int v = 0; v < n; v++) {
if(!visited[v] && graph[u][v] != INF) {
if(graph[u][v] + d[u] < d[v]) { //如果通过u点到达v比直接到达v要更小
d[v] = graph[u][v] + d[u]; //优化d[v]
pre[v].clear(); //清空之前存储的v的前一个节点
pre[v].push_back(u); //将u当成v的前一个节点存储进pre[v]中
} else if(graph[u][v] + d[u] == d[v]) { //如果通过u点到达v与直接到达v点的路径相等
pre[v].push_back(u); //将u加入pre[v]中
}
}
}
}
}
void dfs(int nowVisit) {
tempPath.push_back(nowVisit); //将当前节点加入当前路径中
if(nowVisit == c1) { //如果当前节点就是起点c1,这是递归的终止条件
count++; //最短路径条数加1
int value = 0; //计算当前路径所能搜集到的急救队数量
for(int i = tempPath.size() - 1; i >= 0; i--) {
value += teams[tempPath[i]];
}
if(value > optValue) { //如果当前路径所能搜集到的急救队数量大于最大数量optValue
optValue = value; //更新最大急救队数量的值
path = tempPath; //将当前路径作为能搜集到最大急救队数量的路径保存下来
}
tempPath.pop_back(); //弹出当前节点,变量的手动回溯过程
return;
}
for(int i = 0; i < pre[nowVisit].size(); i++) {
dfs(pre[nowVisit][i]); //深度优先遍历当前节点的所有前一个节点
}
tempPath.pop_back(); //弹出当前节点,变量的手动回溯过程
}
C++解题报告:
思路三:Bellman-Ford算法(邻接矩阵实现)
最短路径的求解方法、有多重标尺时的做法均与Dijkstra算法相同。唯一要注意的是统计最短路径条数的做法:由于Bellman-Ford算法期间会多次访问曾经访问过的顶点,如果按照Dijkstra算法中num数组的写法,将会反复累计已经计算过的顶点。为了解决这个问题,需要设置记录前驱的数组set<int> pre[501],当遇到一条和已有最短路径长度相同的路径时,必须重新计算最短路径条数。
时间复杂度是O(N ^ 3)。空间复杂度是O(N ^ 2)。
C++代码:
#include<iostream>
#include<set>
using namespace std;
int n; //城市数量
int m; //路径数量
int c1; //你所在的城市
int c2; //需要去急救的城市
int INF = 1000000000; //无穷大数
int graph[501][501]; //无向图
int teams[501] = {0}; //每个城市的急救队数量
int d[501]; //d[i]表示从起点c1到达城市i的最短路径长度
int num[501]; //num[i]表示从起点c1到达点i的最短路径条数
int weight[501]; //weight[i]表示从起点c1到达点i所能搜集到的最大急救队数量
set<int> pre[501]; //存储前一节点列表
bool bellmanFord(int s);
int main() {
cin >> n >> m >> c1 >> c2;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
graph[i][j] = INF;
}
}
for(int i = 0; i < n; i++) {
cin >> teams[i];
}
int city1, city2, len;
for(int i = 0; i < m; i++) {
cin >> city1 >> city2 >> len;
graph[city1][city2] = len;
graph[city2][city1] = len;
}
bellmanFord(c1);
cout << num[c2] << " " << weight[c2] << endl;
return 0;
}
bool bellmanFord(int s) {
for(int i = 0; i < n; i++) {
d[i] = INF;
num[i] = 0;
weight[i] = 0;
}
d[s] = 0;
num[s] = 1;
weight[s] = teams[s];
for(int i = 0; i < n - 1; i++) {
for(int u = 0; u < n; u++) {
for(int v = 0; v < n; v++) {
if(graph[u][v] != INF) {
if(graph[u][v] + d[u] < d[v]) {
d[v] = graph[u][v] + d[u];
num[v] = num[u];
weight[v] = weight[u] + teams[v];
pre[v].clear();
pre[v].insert(u);
}else if(graph[u][v] + d[u] == d[v]){
if(weight[u] + teams[v] > weight[v]){
weight[v] = weight[u] + teams[v];
}
pre[v].insert(u);
num[v] = 0; //重新统计num[v]
set<int>::iterator it;
for(it = pre[v].begin(); it != pre[v].end(); it++){
num[v] += num[*it];
}
}
}
}
}
}
for(int u = 0; u < n; u++) {
for(int v = 0; v < n; v++) {
if(graph[u][v] != INF) {
if(graph[u][v] + d[u] < d[v]) {
return false;
}
}
}
}
return true;
}
C++解题报告:
思路四:Bellman-Ford算法+深度优先遍历(邻接矩阵实现)
时间复杂度是O(N ^ 3)。空间复杂度是O(N ^ 2)。
C++代码:
#include<iostream>
#include<set>
#include<vector>
using namespace std;
int n; //城市数量
int m; //路径数量
int c1; //你所在的城市
int c2; //需要去急救的城市
int INF = 1000000000; //无穷大数
int graph[501][501]; //无向图
int teams[501] = {0}; //每个城市的急救队数量
int d[501]; //d[i]表示从起点c1到达城市i的最短路径长度
set<int> pre[501]; //存储前一节点列表
vector<int> path;
vector<int> tempPath;
int optValue = 0;
int count = 0;
bool bellmanFord(int s);
void dfs(int nowVisit);
int main() {
cin >> n >> m >> c1 >> c2;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
graph[i][j] = INF;
}
}
for(int i = 0; i < n; i++) {
cin >> teams[i];
}
int city1, city2, len;
for(int i = 0; i < m; i++) {
cin >> city1 >> city2 >> len;
graph[city1][city2] = len;
graph[city2][city1] = len;
}
bellmanFord(c1);
dfs(c2);
cout << count << " " << optValue << endl;
return 0;
}
bool bellmanFord(int s) {
for(int i = 0; i < n; i++) {
d[i] = INF;
}
d[s] = 0;
for(int i = 0; i < n - 1; i++) {
for(int u = 0; u < n; u++) {
for(int v = 0; v < n; v++) {
if(graph[u][v] != INF) {
if(graph[u][v] + d[u] < d[v]) {
d[v] = graph[u][v] + d[u];
pre[v].clear();
pre[v].insert(u);
}else if(graph[u][v] + d[u] == d[v]){
pre[v].insert(u);
}
}
}
}
}
for(int u = 0; u < n; u++) {
for(int v = 0; v < n; v++) {
if(graph[u][v] != INF) {
if(graph[u][v] + d[u] < d[v]) {
return false;
}
}
}
}
return true;
}
void dfs(int nowVisit){
if(nowVisit == c1){
count++;
tempPath.push_back(nowVisit);
int value = 0;
for(int i = tempPath.size() - 1; i >= 0; i--){
value += teams[tempPath[i]];
}
if(value > optValue){
optValue = value;
path = tempPath;
}
tempPath.pop_back();
}
tempPath.push_back(nowVisit);
set<int>::iterator it;
for(it = pre[nowVisit].begin(); it != pre[nowVisit].end(); it++){
dfs(*it);
}
tempPath.pop_back();
}
C++解题报告:
思路五:Bellman-Ford算法(邻接表实现)
时间复杂度是O(N * M)。空间复杂度是O(N ^ 2)。
C++代码:
#include<iostream>
#include<set>
#include<vector>
using namespace std;
struct node {
int v; //邻接边的目标顶点
int dis; //邻接边的边权
node(int _v, int _dis) : v(_v), dis(_dis) {} //构造函数
};
int n; //城市数量
int m; //路径数量
int c1; //你所在的城市
int c2; //需要去急救的城市
int INF = 1000000000; //无穷大数
vector<node> graph[501]; //无向图
int teams[501] = {0}; //每个城市的急救队数量
int d[501]; //d[i]表示从起点c1到达城市i的最短路径长度
int num[501]; //num[i]表示从起点c1到达点i的最短路径条数
int weight[501]; //weight[i]表示从起点c1到达点i所能搜集到的最大急救队数量
set<int> pre[501]; //存储前一节点列表
bool bellmanFord(int s);
int main() {
cin >> n >> m >> c1 >> c2;
for(int i = 0; i < n; i++) {
cin >> teams[i];
}
int city1, city2, len;
for(int i = 0; i < m; i++) {
cin >> city1 >> city2 >> len;
graph[city1].push_back(node(city2, len));
graph[city2].push_back(node(city1, len));
}
bellmanFord(c1);
cout << num[c2] << " " << weight[c2] << endl;
return 0;
}
bool bellmanFord(int s) {
for(int i = 0; i < n; i++) {
d[i] = INF;
num[i] = 0;
weight[i] = 0;
}
d[s] = 0;
num[s] = 1;
weight[s] = teams[s];
for(int i = 0; i < n - 1; i++) {
for(int u = 0; u < n; u++) {
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v; //邻接边的顶点
int dis = graph[u][j].dis; //邻接边的边权
if(dis + d[u] < d[v]) {
d[v] = dis + d[u];
num[v] = num[u];
weight[v] = weight[u] + teams[v];
pre[v].clear();
pre[v].insert(u);
} else if(dis + d[u] == d[v]) {
if(weight[u] + teams[v] > weight[v]) {
weight[v] = weight[u] + teams[v];
}
pre[v].insert(u);
num[v] = 0; //重新统计num[v]
set<int>::iterator it;
for(it = pre[v].begin(); it != pre[v].end(); it++) {
num[v] += num[*it];
}
}
}
}
}
for(int u = 0; u < n; u++) {
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v; //邻接边的顶点
int dis = graph[u][j].dis; //邻接边的边权
if(dis + d[u] < d[v]) {
return false;
}
}
}
return true;
}
C++解题报告:
思路六:SPFA算法(邻接表实现)
期望时间复杂度是O(kM),其中k是一个常数,在很多情况下k不超过2,可见这个算法异常高效,并且经常性地由于堆优化额Dijkstra算法。
C++代码:
#include<iostream>
#include<set>
#include<vector>
#include<queue>
using namespace std;
struct node {
int v; //邻接边的目标顶点
int dis; //邻接边的边权
node(int _v, int _dis) : v(_v), dis(_dis) {} //构造函数
};
int n; //城市数量
int m; //路径数量
int c1; //你所在的城市
int c2; //需要去急救的城市
int INF = 1000000000; //无穷大数
vector<node> graph[501]; //无向图
int teams[501] = {0}; //每个城市的急救队数量
int d[501]; //d[i]表示从起点c1到达城市i的最短路径长度
int num[501]; //num[i]表示从起点c1到达点i的最短路径条数
int weight[501]; //weight[i]表示从起点c1到达点i所能搜集到的最大急救队数量
set<int> pre[501]; //存储前一节点列表
bool inQueue[501] = {false}; //inQueue[i]用以判断节点i是否在队列中
int countInQueue[501] = {0}; //countInQueue[i]表示节点i的入队次数
bool spfa(int s);
int main() {
cin >> n >> m >> c1 >> c2;
for(int i = 0; i < n; i++) {
cin >> teams[i];
}
int city1, city2, len;
for(int i = 0; i < m; i++) {
cin >> city1 >> city2 >> len;
graph[city1].push_back(node(city2, len));
graph[city2].push_back(node(city1, len));
}
spfa(c1);
cout << num[c2] << " " << weight[c2] << endl;
return 0;
}
bool spfa(int s) {
for(int i = 0; i < n; i++) {
d[i] = INF;
num[i] = 0;
weight[i] = 0;
}
d[s] = 0;
num[s] = 1;
weight[s] = teams[s];
queue<int> q;
q.push(s); //源点入队
inQueue[s] = true;
countInQueue[s]++;
while(!q.empty()) {
int u = q.front();
q.pop();
inQueue[u] = false; //设置u为不在队列中
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v; //邻接边的顶点
int dis = graph[u][j].dis; //邻接边的边权
if(dis + d[u] < d[v]) {
d[v] = dis + d[u];
if(!inQueue[v]){ //如果v不在队列中
q.push(v); //v入队
inQueue[v] = true; //设置v为在队列中
countInQueue[v]++;
if(countInQueue[v] >= n){
return false; //当某个顶点的入队次数超过n - 1次时,说明图中存在从源点可达的负环
}
}
num[v] = num[u];
weight[v] = weight[u] + teams[v];
pre[v].clear();
pre[v].insert(u);
} else if(dis + d[u] == d[v]) {
if(weight[u] + teams[v] > weight[v]) {
weight[v] = weight[u] + teams[v];
}
pre[v].insert(u);
num[v] = 0; //重新统计num[v]
set<int>::iterator it;
for(it = pre[v].begin(); it != pre[v].end(); it++) {
num[v] += num[*it];
}
if(!inQueue[v]){ //如果v不在队列中
q.push(v); //v入队
inQueue[v] = true; //设置v为在队列中
countInQueue[v]++;
if(countInQueue[v] >= n){
return false; //当某个顶点的入队次数超过n - 1次时,说明图中存在从源点可达的负环
}
}
}
}
}
return true;
}
C++解题报告:
思路七:SPFA算法+深度优先遍历(邻接表实现)
期望时间复杂度是O(kM),其中k是一个常数,在很多情况下k不超过2,可见这个算法异常高效,并且经常性地优于堆优化的Dijkstra算法。
C++代码:
#include<iostream>
#include<queue>
#include<set>
#include<vector>
using namespace std;
struct node{
int v;
int length;
node(int _v, int _length){
v = _v;
length = _length;
}
};
int N, M, C1, C2, INF = 1000000000, teams[500], d[500], countInq[500], count = 0, maxTeams = 0, city1, city2, length;
vector<node> graph[500];
bool inq[500];
set<int> pre[500];
vector<int> tempPath;
bool spfa(int s);
void dfs(int nowVisit);
int main(){
scanf("%d %d %d %d", &N, &M, &C1, &C2);
for(int i = 0; i < N; i++){
scanf("%d", &teams[i]);
}
for(int i = 0; i < M; i++){
scanf("%d %d %d", &city1, &city2, &length);
graph[city1].push_back(node(city2, length));
graph[city2].push_back(node(city1, length));
}
spfa(C1);
dfs(C2);
printf("%d %d\n", count, maxTeams);
return 0;
}
bool spfa(int s){
fill(d, d + N, INF);
fill(countInq, countInq + N, 0);
fill(inq, inq + N, false);
d[s] = 0;
queue<int> q;
q.push(s);
countInq[s]++;
inq[s] = true;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = false;
for(int j = 0; j < graph[u].size(); j++){
int v = graph[u][j].v;
int length = graph[u][j].length;
if(d[u] + length < d[v]){
d[v] = d[u] + length;
pre[v].clear();
pre[v].insert(u);
if(!inq[v]){
q.push(v);
inq[v] = true;
countInq[v]++;
if(countInq[v] > N - 1){
return false;
}
}
}else if(d[u] + length == d[v]){
pre[v].insert(u);
if(!inq[v]){
q.push(v);
inq[v] = true;
countInq[v]++;
if(countInq[v] > N - 1){
return false;
}
}
}
}
}
return true;
}
void dfs(int nowVisit){
tempPath.push_back(nowVisit);
if(nowVisit == C1){
count++;
int countTeams = 0;
for(int i = 0; i < tempPath.size(); i++){
countTeams += teams[tempPath[i]];
}
if(countTeams > maxTeams){
maxTeams = countTeams;
}
tempPath.pop_back();
return;
}
for(set<int>::iterator it = pre[nowVisit].begin(); it != pre[nowVisit].end(); it++){
dfs(*it);
}
tempPath.pop_back();
}
C++解题报告: