【PTA练习】L2-001 紧急救援 (25 分! 18分没a必看)Dijkstra模板以及本题测试点详解

L2-001 紧急救援 (25 分)

试题链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805073643683840
这次自己总结一下Dijkstra算法!(题目在后半部分,看题解的直接跳转后半段吧)
详细总结请看这个我的这篇:
https://blog.csdn.net/qq_36931762/article/details/88806872


Dijkstra算法

这里先大概给大家说一下Dijkstra算法邻接矩阵代码模板:(适合图的点数不超过1000的情况)
代码:

//全局变量部分
const int MAXN = 1000;  //图的最大顶点数
const int INF = 1000000000; //设立INF为一个极大数,
            //这里这个数也可以设置为0x3f3f3f3f,这代表无穷大,即1061109567这一数值

int n;                  //当前图的点数
int vis[MAXN];          //标记数组用于记录图中各个点的被访问情况,0为未访问,1为已访问
int dis[MAXN];          //记录起点到各个顶点的最短路径长度
int m[MAXN][MAXN];      //用一个邻接矩阵来记录图中各个点的连接情况

void Dijkstra(int s){   //s为起点
    fill(vis, vis+MAXN, 0); //将标记数组初始化为0即未被访问状态,此处可用memset
    fill(dis, dis+MAXN, INF);   //将最短路数组初始化为一个很大的数,此处要注意不可用memset
    dis[s]=0;           //首先将起点s到达自身的最短路设为0
    for(int i=0;i<n;i++){   //n次循环,遍历完n个点   
        int node = -1;      //node记录当前能找到的从起点到此没被访问的最短的一个点
        int minn = INF;     //minn记录到达那个点的最短路径
        for(int j=0;j<n;j++){   //每个点逐步寻找没被访问的最短的一个点
            if(vis[j]==0 && dis[j]<minn){   
                minn = dis[j];
                node = j;
            }
        }
        if(node==-1){   //找不到小于INF的一个点,说明其他结点与定点不连通
            return ;
        }
        vis[node] = 1;  //将当前点标记为1
        for(int j=0;j<n;j++){
            if(vis[j]==0 && m[node][j]!=INF && (dis[node]+m[node][j])<dis[j]){
                //如果结点未被访问且 node能到达此结点 并且从起点到此结点过node中介点更近
                //在此标尺只有距离,如果有两条距离相等或多条相等,那么就只需在此进行修改
                //如路径相同第二标尺为花费时,则可通过在增加花费数组,在此if后加elseif判断距离相等情况花费不同条件。
                //下题就有两标尺。
                dis[j] = dis[node]+m[node][j];
            }
        }
    }
}


题目:

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

大致思路: 思路就是Dijsktra算法思路,然后在其Dijkstra基本算法模板的基础上稍加修改,然后增加了一个限制条件,在利用一点并查里的find思想记录一下当前结点的前一结点从而还原最短路径,由于要求最短路径由起点到终点所以就用一个vector数组来从后往前存放,然后在从后往前遍历输出即形成从起点到终点的路径。
具体代码如下:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

const int INF=100000000;

int saveteam[505];int st[505];
int mm[505][505], number[505];
int vis[505];
int dis[505];
int father[505];
int n, m, s, d;

void Dijkstra(){
    fill(dis, dis+n, INF);
    fill(vis, vis+n, 0);
    fill(number, number+n, 0);
    dis[s] = 0;
    st[s] = saveteam[s];
    number[s] = 1;
    for(int i=0;i<n;i++){
        int minn = INF;int node = -1;
        for(int j=0;j<n;j++){
            if(vis[j]==0 && dis[j]<minn){
                minn = dis[j];
                node = j;
            }
        }
        vis[node] = 1;
        if(node==-1){
            return;
        }
        for(int j=0;j<n;j++){
            if(vis[j]==0 && (dis[node] + mm[node][j])<dis[j] && mm[node][j]!=-1){
                dis[j] = dis[node] + mm[node][j];
                st[j] = st[node] + saveteam[j];
                father[j] = node;
                number[j] = number[node];
            }
            else if(vis[j]==0 && (dis[node] + mm[node][j])==dis[j] && mm[node][j]!=-1){	//此处就是判断第二标尺情况,哪条路径上得到救援队最多
                if((st[node] + saveteam[j]) > st[j]){
                    st[j] = st[node] + saveteam[j];
                    father[j] = node;
                }
                number[j] += number[node] ;
                //这里很关键,不能简单的记录为number[j]++,我一开始就错在这里了。出错样例见后面
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    vector <int> ve;
    cin>>n>>m>>s>>d;
    for(int i=0;i<n;i++){
        cin>>saveteam[i];
    }
    for(int i=0;i<n;i++){
        father[i] = i;
        st[i] = saveteam[i];
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            mm[i][j] = -1;
        }
    }
    for(int i=0;i<m;i++){
        int n1, n2, value;
        cin>>n1>>n2>>value;
        mm[n1][n2] = value;
        mm[n2][n1] = value;
    }
    Dijkstra();
    int p = d, sum=0;
    while(1){
        if(p==father[p]){
            ve.push_back(p);
            break;
        }
        ve.push_back(p);
        p = father[p];
    }
    cout<<number[d]<<" "<<st[d]<<endl;
    for(int i=ve.size()-1;i>=0;i--){
        if(i==ve.size()-1){
            cout<<ve[i];
        }else{
            cout<<" "<<ve[i];
        }
    }
    return 0;
}
/*  如果你没有ac这个题可以用以下样例进行测试,测试样例图我会在后面给大家展示一下
样例(1)
6 6 0 3
30 10 20 50 20 20
0 1 1
1 2 2
2 3 3
0 4 1
4 5 2
5 3 3

样例(2)
6 7 0 3
30 100 20 50 20 20
0 1 1
1 3 5
1 2 2
2 3 3
0 4 1
4 5 2
5 3 3

样例(3)
5 5 0 3
30 100 20 50 120
0 1 1
1 2 1
2 3 3
4 3 5
0 4 1

样例(4)
7 8 0 6
10 20 30 50 30 30 20
0 1 1
0 2 1
1 3 2
2 3 2
3 4 3
3 5 3
4 6 4
5 6 4

*/

样例(1)
在这里插入图片描述
样例(2)
在这里插入图片描述
样例(3)
在这里插入图片描述
样例(4)
在这里插入图片描述基本上上面四个样例能过就能过了!!!

之前在天梯赛训练的时候总是觉得自己能力不行做不到这方面的题就一直对这方面的题疏忽练习,然后由于平时也懒得真的把算法模板思考透彻,所以平时就只在算法课上照着模板敲然后解题,但是这次为了下周的天梯赛还是有必要把这些基本算法都彻底搞明白的,也把题目搞明白,所以就一鼓作气总结理解了一下,希望也能对你的学习有帮助,继续坚持!一起加油!

根据提供的引用内容,这是一道最短路径问题,需要使用Dijkstra算法来解决。以下是解决该问题的步骤: 1.读取输入数据,包括城市数量N、道路数量M、起点S、终点D、每个城市的救援队数量以及每条道路的起点、终点和长度。 2.初始化图的邻接矩阵,将所有边的权重设置为无穷大,将起点到起点的距离设置为0。 3.使用Dijkstra算法计算起点到所有其他点的最短路径。具体步骤如下: a.初始化一个集合S,用于存储已经找到最短路径的点。 b.初始化一个数组dist,用于存储起点到每个点的最短距离,初始值为无穷大。 c.将起点加入集合S中,将起点到起点的距离dist[S]设置为0。 d.对于起点的每个邻居节点v,更新起点到v的距离dist[v]为min(dist[v], dist[S] + weight(S, v)),其中weight(S, v)表示边(S, v)的权重。 e.从未加入集合S中的节点中选择一个距离起点最近的节点u,将其加入集合S中。 f.重复步骤d和e,直到所有节点都加入集合S中或者有可加入的节点。 4.使用回溯法找到起点到终点的路径,并计算路径上的最大救援队数量。具体步骤如下: a.从终点开始,沿着最短路径向前回溯,直到回溯到起点。 b.记录路径上的每个城市的救援队数量,取其中的最大值。 c.将路径上的城市按顺序输出。 5.输出结果,包括最短路径的条数和能够召集的最多的救援队数量,以及从S到D的路径中经过的城市编号。 以下是Python代码实现: ```python import sys # 读取输入数据 N, M, S, D = map(int, input().split()) teams = list(map(int, input().split())) graph = [[sys.maxsize] * N for _ in range(N)] for i in range(M): u, v, w = map(int, input().split()) graph[u][v] = graph[v][u] = w # Dijkstra算法计算最短路径 dist = [sys.maxsize] * N dist[S] = 0 count = [0] * N count[S] = teams[S] visited = set() while len(visited) < N: u = min(set(range(N)) - visited, key=dist.__getitem__) visited.add(u) for v in range(N): if v not in visited and graph[u][v] != sys.maxsize: if dist[u] + graph[u][v] < dist[v]: dist[v] = dist[u] + graph[u][v] count[v] = count[u] + teams[v] elif dist[u] + graph[u][v] == dist[v]: count[v] = max(count[v], count[u] + teams[v]) # 回溯法找到最短路径 path = [] u = D while u != S: path.append(u) for v in range(N): if graph[u][v] != sys.maxsize and dist[u] == dist[v] + graph[u][v]: u = v break path.append(S) path.reverse() # 输出结果 print(str(len(path)) + ' ' + str(count[D])) print(' '.join(str(p) for p in path)) ```
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值