PTA 城市间紧急救援 分数 25 作者 陈越 单位 浙江大学

2023-12-13 08:55:52

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

输入第一行给出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

代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB

答案:

# include <stdio.h>
# include <stdlib.h>
# define INF 999
# define MAXV 510

int Path[MAXV], Dist[MAXV], Right[MAXV];//Path存储最短路径上的前驱节点,Dist存储起点到各节点的最短路径权值和,Right存储起点到各节点的节点权值和
int cnt[MAXV];//存储起点到各节点的最短路径数量

typedef struct GNode
{
	int V[MAXV];//节点
	int E[MAXV][MAXV];//邻接矩阵
	int W[MAXV];//节点权值
	int n, m;
}Graph;

Graph* CreateGraph(int n, int m)
{
	Graph* G;
	G = (Graph*)malloc(sizeof(Graph));
	int city1, city2, length;//输入数据
	G->n = n;
	G->m = m;
	for (int i = 0; i < n; i++)G->V[i] = i;//初始化
	for (int i = 0; i < n; i++)//初始化
	{
		for (int j = 0; j < n; j++)G->E[i][j] = INF;
	}

	for (int i = 0; i < n; i++)scanf("%d", &G->W[i]);//输入节点权值
	for (int i = 0; i < m; i++)//输入邻接矩阵
	{
		scanf("%d %d %d", &city1, &city2, &length);
		G->E[city1][city2] = length;
		G->E[city2][city1] = length;
	}

	return G;
}

void Dijkstra(Graph* G,int s)
{
	int final[MAXV];//标记数组,表示节点是否已经加入最短路径树
	int k;//后面会用
	for (int i = 0; i < G->n; i++)//初始化
	{
		final[i] = 0;//全部顶点初始化未加入最短路径树状态
		Dist[i] = G->E[s][i];//将与s有连线的节点加上权值,其余为INF
		Path[i] = s;//初始化路径数组
		cnt[i] = 1;//初始时默认各节点都仅有一条最短路径
		Right[i] = 0;//默认都为0
	}
	Dist[s] = 0;//s至s路径为0
	final[s] = 1;//s加入最短路径树
	Right[s] = G->W[s];//s到s的权值和为起点的权值

	for (int i = 1; i < G->n; i++)//开始主循环,每次求得s到某个顶点i的最短路径。因为s已加入最短路径树,还有n - 1个未加入,故从i = 1开始
	{
		int min = INF;//当前所知离s顶点的最近距离,初始为最大
		for (int j = 0; j < G->n; j++)
		{
			if (!final[j] && Dist[j] < min)//找到未加入最短路径树的节点距最短路径树的最小距离
			{
				k = j;
				min = Dist[j];
			}
		}
		
        if (i == 1)//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!因为i==1时找到的节点并未对Right进行处理,并且距离==min的节点也不一定只有一个,所以要对此情况额外处理一下
        {
            for (int j = 0; j < G->n; j++)
            {
                if (!final[j] && Dist[j] == min && Right[j] > Right[k])//找到距离等于min并且权值最大的节点(根据题目要求“一路上召集尽可能多的救援队”)
                {
                    k = j;
                }
            }
            for (int j = 0; j < G->n; j++)//对每个距离等于min的节点的Right值进行处理
            {
                if (!final[j] && Dist[j] == min)
                {
                    Right[j] = G->W[s] + G->W[j];
                }
            }
        }
        
        final[k] = 1;//将节点加入最短路径树

		for (int j = 0; j < G->n; j++)//修正当前最短路径及距离、权值
		{
			if (!final[j] && (min + G->E[k][j] < Dist[j]))//如果经过k顶点到j顶点的路径比到j顶点本来的路径短的话
			{
				//说明找到了更短的路径
				Dist[j] = min + G->E[k][j];
				Path[j] = k;
				Right[j] = Right[k] + G->W[j];
				cnt[j] = cnt[k];
			}
			else if (!final[j] && (min + G->E[k][j] == Dist[j]))//如果经过k顶点到j顶点的路径与到j顶点本来的路径相等的话
			{
				cnt[j] += cnt[k];//说明有多条最短路径,这里修改到j节点的最短路径的数目
				if (Right[j] < Right[k] + G->W[j])//这里选择能召集最多救援队的路径
				{
					Right[j] = Right[k] + G->W[j];
					Path[j] = k;
				}
			}
		}
	}
}

void Print(int s, int d)
{
	printf("%d %d\n", cnt[d], Right[d]);
	int Stack[MAXV];
	int top = -1;
	int k = d;
	Stack[++top] = k;
	while (1)
	{
		if (k == s)break;
		Stack[++top] = Path[k];
		k = Path[k];
	}
	while (top != -1)
	{
		if (top != 0)printf("%d ", Stack[top]);
		else printf("%d", Stack[top]);
		top--;
	}
}

int main()
{
	int n, m, s, d;
	scanf("%d %d %d %d", &n, &m, &s, &d);
	Graph* G;
	G = CreateGraph(n, m);
	Dijkstra(G, s);
	Print(s, d);
	return 0;
}

下面代码中的Dijkstra算法处理得更简单,但是封装得不太好,上面代码的Dijkstra算法可以借鉴下面。

值得提的一点是,做工程项目时一定要按照第一段代码去写,要封装好,这样才能复用,才足够稳定。
而参加蓝桥杯、acm等竞赛时,可以按照第二段代码的方式去写,因为比赛就是要求快速解题,一次通过就行,不会再复用。

#include <stdio.h>
#include <string.h>

#define N 1010

int n, m, S, D;
int g[N][N], w[N];  // g 存储图的边权,w 存储每个节点的权值
int st[N];          // 标记数组,表示节点是否已经加入最短路径树
int dist[N], r[N], path[N];  // dist 存储起点到各节点的最短距离,r 存储起点到各节点的权值和,path 存储最短路径上的前驱节点
int cnt[N];  // cnt 存储起点到各节点的最短路径数量

void dijkstra(int u, int s) {
    memset(dist, 0x3f, sizeof(dist));  // 初始化距离数组为无穷大
    dist[u] = 0;  // 起点到自身的距离为0
    r[u] = w[u];  // 起点到自身的权值和为起点的权值
    for (int i = 0; i < n; i++) 
    {
        int t = -1;
        for (int j = 0; j < n; j++) {
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;  // 选择未加入最短路径树的距离最小的节点
        }
        st[t] = 1;  // 将节点加入最短路径树

        for (int j = 0; j < n; j++) {
            if (dist[j] > dist[t] + g[t][j]) {
                dist[j] = dist[t] + g[t][j];
                path[j] = t;
                r[j] = r[t] + w[j];
                cnt[j] = cnt[t];
            } else if (dist[j] == dist[t] + g[t][j]) {
                cnt[j] += cnt[t];
                if (r[j] < r[t] + w[j]) {
                    r[j] = r[t] + w[j];
                    path[j] = t;
                }
            }
        }
    }
    printf("%d %d\n", cnt[s], r[s]);  // 输出最短路径数量和最短路径权值和
    int q[N];
    int k = s, idx = 0;
    q[idx++] = k;
    while (1) {
        if (k == u)
            break;
        q[idx++] = path[k];
        k = path[k];
    }
    for (int i = idx - 1; i >= 0; i--) {
        if (i != 0)
            printf("%d ", q[i]);
        else
            printf("%d\n", q[i]);
    }
}

int main() {
    scanf("%d%d%d%d", &n, &m, &S, &D);
    for (int i = 0; i < n; i++)
        scanf("%d", &w[i]), cnt[i] = 1;

    memset(g, 0x3f, sizeof(g));  // 初始化图的边权为无穷大
    while (m--) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = c;  // 读入图的边权信息
    }
    dijkstra(S, D);  // 调用Dijkstra算法求解最短路径

    return 0;
}

文章来源:https://blog.csdn.net/m0_73804746/article/details/134944416
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。