【PAT甲级题解记录】1003 Emergency (25 分)

【PAT甲级题解记录】1003 Emergency (25 分)

前言

Problem:1003 Emergency (25 分)

Tags:单源最短路径 dijkstra

Difficulty:剧情模式 想流点汗 想流点血 死而无憾

Address:1003 Emergency (25 分)

问题描述

  • 给定一个城市间构成的一个无向图,每个点上都有一个救援人手数量,一个救援队需要从点 C 1 C1 C1 到点 C 2 C2 C2 ,途经的城市中的救援人手求最短路径的数量以及满足路径最短的途经的最大人手数量和。

  • 简单来说,就是一个带权点的无向图,求其单源最短路径的数量以及最短路径情况下的最大路径点权和。

解题思路

  1. 对于单源最短路径(这道题也应该不会出现负边),一般我们最常用的就是dijkstra算法,但这里不单单是求最短路径,但是依旧可以用这个算法,算是dijkstra求最短路径的变题。最短路径我也总结了一个模板在这里:最短路径一般算法总结(板子),可以直接复制下来改。

  2. 在此基础上,为了求最短路径数量,我们加入了类似dp的思想:

    • c n t [ i ] cnt[i] cnt[i] 表示到 i i i 点的最短路径数量。然后在dijkstra算法的每一次更新路径长度数组dist时更新每一个点的cnt值即可。

    • n u m [ i ] num[i] num[i] 表示当前最短路径下到 i i i 点的最大救援人员,同样在更新最新dist时更新num值,但是不同的是这时候的更新时有条件的(肯定要人员更多才更新)。

    • 即分为俩种情况:

      • 搜索到相同路径长度的前节点时:

        cnt[i] = cnt[i_pre]+cnt[i]; 
        

        若当前方案救援人数更多时:

        num[i] = num[i_pre]+num_of_rescue[i];
        
      • 搜索到更短路径长度的前节点时:

        cnt[i] = cnt[i_pre]; 
        num[i] = num[i_pre]+num_of_rescue[i];
        

这道题在atcoder上也出现过,但那个版本更简单:https://atcoder.jp/contests/abc211/tasks/abc211_d

参考代码

#include<iostream>
#include<vector>

using namespace std;
int N; // number of cities
int M; // number of roads
int C1, C2; // start, end
vector<int> teams(505, 0); // number of rescue teams in the i-th city
vector<int> dist(505, 0x3f3f3f3f); // 初始化为大值,表示无路径
vector<bool> in(505, false);
vector<vector<int> > MAP(505);
vector<int> cnt(505, 0);
vector<int> teams_sum(505, 0); // 按照当前最短路径,到某一点可遇到的救援队最多数量

void init() {

    cin >> N >> M >> C1 >> C2;
    for (int i = 0; i < N; i++) {
        cin >> teams[i];
    }
    for (int i = 0; i < N; i++) {
        MAP[i].resize(N, 0x3f3f3f3f);
    }
    for (int i = 0; i < M; i++) {
        int c1, c2, L;
        cin >> c1 >> c2 >> L;
        MAP[c1][c2] = L;
        MAP[c2][c1] = L;
    }
}

void dijkstra() {
    dist[C1] = 0; // 初始化dist数组,这样可以确保第一次循环可以顺利得出距离集合最短的路径,即起点本身
    teams_sum[C1] = teams[C1];
    cnt[C1] = 1;
    int times = N;
    while (times--) { // loop for N times
        int min_dist = 0x3f3f3f3f;
        int min_city = -1;
        // find min_dist
        for (int i = 0; i < N; i++) {
            if (!in[i] && dist[i] < min_dist) {
                min_dist = dist[i];
                min_city = i;
            }
        }
        if (min_city == C2) break;
        in[min_city] = true;

        // update dist
        for (int i = 0; i < N; i++) {
            int new_dist_toi = dist[min_city] + MAP[min_city][i]; // 通过 min_city 到 i 的 最短路径
            int new_teams_num_toi = teams_sum[min_city] + teams[i];  // 通过 min_city 到 i 的 最大救援队数量

            if (!in[i] && new_dist_toi < dist[i]) {
                dist[i] = new_dist_toi;
                teams_sum[i] = new_teams_num_toi;
                cnt[i] = cnt[min_city];
            } else if (!in[i] && new_dist_toi == dist[i]) { // 注意一定要加 else,否则前一个 if 会影响该判断
                cnt[i] = cnt[min_city] + cnt[i];
                if (new_teams_num_toi > teams_sum[i]) {
                    teams_sum[i] = new_teams_num_toi;
                }
            }
        }

    }
}

void solution_1003() {
    init();
    dijkstra();
    cout << cnt[C2] << " " << teams_sum[C2] << endl;
}
int main() {
    solution_1003();
    return 0;
}

总结

没啥好说的算是比较基础的最短路径问题,但是要记住理解也需要花点功夫,毕竟再熟悉的算法隔一段时间后也可能会忘。

再把最短路径的板子放一遍:最短路径一般算法总结(板子)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值