L2-001 紧急救援 (25 分)——单源最短路


题目:

作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。

输入格式:

输入第一行给出4个正整数N、M、S、D,其中N(2N500)是城市的个数,顺便假设城市的编号为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

分析: 

本题很容易想到用dijstra算法计算最短路,但在dijstra算法模板上又加了一些内容,通过学习本题,我学会了如何用数组来保存路径,用栈输出路径。同时对于这种比较复杂的最短路问题有了一定的心得。长话短说,我们肯定明白一点,本题路径最短>救援队人数最多。所以我们先考虑路径问题,当路径相同时我们再考虑人数问题。当然,在编程的时候我们还要时刻记录路径和相应人数的变化。(不太擅长说明啊~只能慢慢培养了,坚持写题解吧!)

代码:

#include 
#include 
#include 
using namespace std;
const int N = 510;
int g[N][N], dis[N], savetotal[N], save[N], path[2*N], pathtotal[N];//  两点距离   到起点距离  某城市救援总人数 某城市原本就有救援人数 某点的前驱 到某点的最短路总数 
bool vis[N];
int n, m, s, d;

void dijstra()
{
    memset(dis, 0x3f, sizeof dis);
    dis[s] = 0;
    savetotal[s] = save[s];
    path[s] = 600; // 看别人的题解都是赋-1 但是我觉得可能会越界 所以我干脆用大一点的数 反正数据不超过500 这个就是起点了 
    pathtotal[s] = 1;  // @@ 
    for(int i = 0; i < n; i++)
    {
        int t = -1;
        for(int j = 0; j < n; j++)
        if(!vis[j] && (t==-1 || dis[t] > dis[j]))
        t = j;
        
        for(int j = 0; j < n; j++)
        {
            if(dis[j] > dis[t] + g[t][j])
            {
                dis[j] = dis[t] + g[t][j];
                path[j] = t;
                pathtotal[j] = pathtotal[s];
                savetotal[j] = savetotal[t] + save[j];  // 为什么要覆盖? 因为路径优先考虑 
            }
            else if(dis[j] == dis[t] + g[t][j])
            {
                pathtotal[j] += pathtotal[t];
                if(savetotal[t] + save[j] > savetotal[j])  // 当路径相同的时候再考虑人员数量 
                {
                    savetotal[j] = savetotal[t] + save[j];
                    path[j] = t;
                }
            }
        }
        vis[t] = true;
    }
}
int main()
{
    cin >> n >> m >> s >> d;
    memset(g, 0x3f, sizeof g);
    for(int i = 0; i < n; i++) cin >> save[i];
    while(m--)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = c;
    }
    dijstra();
    cout << pathtotal[d] << " " << savetotal[d] << endl;
    stack<int>ss;  // 用栈存路径 
    ss.push(d);  // 先把终点放进去 然后再倒退 
    while(path[d]!=0)  // 因为开始的时候起点的那个值赋为了600 path[600] == 0 
    {
        ss.push(path[d]);
        d = path[d];
    }
    cout << s;  // 起点是不在栈里的 所以要先输出起点 
    while(!ss.empty())
    {
        cout << " " << ss.top();ss.pop();   
    } 
    cout << endl;
    return 0;
}

 最后贴一下战果!!!哈哈哈 开心!