题目

A traveler's map gives the distances between cities along the highways, together with the cost of each highway. Now you are supposed to write a program to help a traveler to decide the shortest path between his/her starting city and the destination. If such a shortest path is not unique, you are supposed to output the one with the minimum cost, which is guaranteed to be unique.

Input Specification:
Each input file contains one test case. Each case starts with a line containing 4 positive integers N, M, S, and D, where N (≤500) is the number of cities (and hence the cities are numbered from 0 to N−1); M is the number of highways; S and D are the starting and the destination cities, respectively. Then M lines follow, each provides the information of a highway, in the format:

City1 City2 Distance Cost
where the numbers are all integers no more than 500, and are separated by a space.

Output Specification:
For each test case, print in one line the cities along the shortest path from the starting point to the destination, followed by the total distance and the total cost of the path. The numbers must be separated by a space and there must be no extra space at the end of output.

Sample Input:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
Sample Output:
0 2 3 3 40

题目解读

给出由n个节点和m条边组成的无向图,给出m条边的距离和代价,给定起点和终点,要求输出从起点到终点的最短路径、最短距离、以及最小代价。

这个题,本质上和 PAT 1018 Public Bike Management 是一样的,无非就是在Dijstra求最短距离的过程中保存最短路径上的前驱,再跟据DFS结合前驱得到全部最短路径,在此过程中计算每一条最短路径的代价,选出最小的那条

思路分析

  • 首先,遇到最短路径,肯定是Dijstra,但题目说了,可能会有多条最短路径,我们要找到全部路径怎么办?把每一个路径都保存下来吗?可以,但没必要。我们只需要记录最短路径上每个节点的直接前驱就可以了,而这个,在Dijstra算法中,只需要在更新过程中(d[v] = d[u] + e[u][v])加上一条语句就可以了(prev[v].push_back(u)),非常简单。如果它有多个最短路径,那么它就有多个直接前驱,因此,每个节点都应该有一个vector来保存它的全部最短路径的直接前驱,最终,就是需要一个vector数组。
  • 找到最短路径显然是不够的,我们还要进行选择,选择那一条最短路径的代价最小。所以,我们要遍历全部最短路径,于是dfs就出场了,因为我们保存的是节点的全部直接前驱,所以需要从终点往起点去寻找。每一次从终点找到起点的路径都是一条最短路径,这其实就是一个DFS,计算这条路径的代价,如果更小,则更新最小代价并选择这条路径。
  • 所以,总的来说就是,Dijstra找到全部最短路径,dfs遍历每个最短路径并找到代价最小的那个

满分代码

注释写的比较详细,思路说的太多反而容易迷糊,建议多看看dfs,这个还是很常用的。

#include <iostream>
#include <algorithm>
#include <climits>
#include <vector>
using namespace std;

// n个顶点m条边,s是起点,d是终点
int n, m, s, d;
// edge保存两个点之间的距离,min_dis保存起点到每个顶点的最短距离,cost保存每两个点之间的代价
int edge[501][501], min_dis[501], cost[501][501];
// 保存每个顶点在 从起点到它的全部最短路径上 的直接前驱
// 假如 0 1 2 4 和 0 3 4都是从0到4的直接前驱,那么pre[4] = {2,3}
vector<int> pre[501];
// dijstra求最短距离时的划分
bool visited[501];

// temppath用于保存起点到终点的全部最短路径中的其中一个,path保存代价最小的最短路径
vector<int> temppath, path;
// 最小代价
int min_cost = INT_MAX;

// 深度优先遍历,对于从起点到终点的全部最短路径,算出每个路径的代价,选择代价最小的那条
void dfs(int v) {
    // 当前节点入当前路径
    temppath.push_back(v);
    // 如果达到起点(我们是根据保存的前缀从后往前搜索)
    if (v == s) {
        // temppath里保存了一条完整的路径,并且是从终点到起点
        int tempcost = 0;
        // 计算这条路上的代价,
        for (int i = temppath.size() - 1; i >= 1; --i) {
            tempcost += cost[temppath[i]][temppath[i - 1]];
        }
        // 如果选择这条最短路径的代价更小
        if (tempcost < min_cost) {
            // 更新最小代价
            min_cost = tempcost;
            // 更新最小代价路径
            path = temppath;
        }
        // "首尾呼应"原则,保持并列关系,不改变tempath
        temppath.pop_back();
        return;
    }
    // 如果每到达起点,那就继续按它的前驱递归,每跳路径(前驱之间)之间是并列关系
    for (auto j: pre[v]) {
        dfs(j);
    }
    // "首尾呼应"原则,保持并列关系,不改变tempath
    temppath.pop_back();
}

int main() {

    // 初始化起点到任意点之间的最短路径无穷大
    fill(min_dis, min_dis + 501, INT_MAX);
    // 初始化任意两点之间距离最大
    fill(edge[0], edge[0] + 501 * 501, INT_MAX);

    cin >> n >> m >> s >> d;

    int f, t, dd, cc;
    // 输入n条边的距离和代价
    while (m-- > 0) {
        cin >> f >> t >> dd >> cc;
        edge[f][t] = edge[t][f] = dd;
        cost[f][t] = cost[t][f] = cc;
    }
    // dijstra初始化,到起点的距离为0
    min_dis[s] = 0;
    // dijstra
    for (int i = 0; i < n; ++i) {
        // 每次选出一个距离最小的点
        int u = -1, min = INT_MAX;
        for (int j = 0; j < n; ++j) {
            if (!visited[j] && min_dis[j] < min) {
                min = min_dis[j];
                u = j;
            }
        }
        // 全部选完了
        if (u == -1) break;
        // 标记到已选择集合
        visited[u] = true;
        // 更新与它相连的全部节点的最短路径
        for (int v = 0; v < n; ++v) {
            if (!visited[v] && edge[u][v] != INT_MAX) {
                // 选择这条路径起点到它的最短距离更小
                if (min_dis[u] + edge[u][v] < min_dis[v]) {
                    // 更新最短距离
                    min_dis[v] = min_dis[u] + edge[u][v];
                    // 清楚之前的路径前驱
                    pre[v].clear();
                    // 保存这个更小的路径上的前驱
                    pre[v].push_back(u);
                // 多了一条最短路径
                } else if (min_dis[u] + edge[u][v] == min_dis[v]) {
                    // 那么把这条路径的直接前驱也加进去
                    pre[v].push_back(u);
                }
            }
        } 
    }
    // 从终点到起点进行dfs
    dfs(d);
    // dfs后temp保存的是起点到终点的最短路径中的代价最小的那个,
    for (int i = path.size() - 1; i >= 0; --i) {
        cout << path[i] << " ";
    }
    // min_dis[d]保存的是起点到终点的最短距离,min_cos保存的是最小的代价
    cout << min_dis[d] << " " << min_cost;

    return 0;
}

Q.E.D.


Think twice, code once.