PTA团体程序设计天梯赛练习集:《L2-001 紧急救援》题解

16 篇文章 0 订阅

题意:

条件:给出M条路、起点和终点以及每个点含有的救援队的数量。

要求:

1. 找出从起点到终点的最短路径的条数
2. 所有最低路径中能召集最多救援队的数量。 
3. 能召集最多救援队的最短路径


思路:
求单源最短路径可以使用Dijstra算法。
复杂度为:500^2 ,不会超时。

Dijstra算法:

思想:

G(V,E)V表示图中所有的顶点,E表示图中所有的边。
对G(V,E)设置一个集合S,存放访问过的顶点,从集合V-S
中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S,
之后,令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离。
这样的操作执行n次,直到S中包含所有顶点。
参考:算法笔记

基本模板(找出从起点到其他各点的最短距离):

const int INF = 0x3f3f3f3f;
//以邻接表形式保存图,稀疏图用邻接表,稠密图用邻接矩阵
typedef struct{
	int next;
	int len;
}
vector<Node> V[N];

bool vis[N];//标记访问过的顶点
int dis[N];//记录从起点开始到其他各点的距离
void Dijstra(int s){
	//起点到自身的距离为0
	dis[s] = 0;
	//循环n次
	for(int i = 0; i < n; i++){
		int u = -1;
		int min = INF;
		//选择与起点s的最短距离最小的一个未访问顶点(记为u)
		for(int j = 0; j < n; j++){
			if(!vis[j] && dis[j] < min){
				u = j;
				min = dis[j] 
			}
		}
		//如果找不到未被访问过的顶点,说明找不到其他与s相通的顶点,跳出循环
		if(u == -1){
			break;
		}
		//标记u为已访问
		vis[u] = true;
		//令顶点u为中介点,优化起点s与所有从u能到达的顶点v之间的最短距离
		for(int j = 0; j < V[u].size(); j++){
			int v = V[u][j].next;
			int len = V[u][j].len;
			//如果v已经访问过,则跳过该点
			if(vis[v]){
				continue;
			}
			//优化
			if(dis[u] + len < dis[v]){
				dis[v] = dis[u] + len;
			}
		}
	}

扩展模板:

1、记录最短路径的条数:

求最短路径条数,增加一个保存最短路径条数的数组num[],令从起点到达顶点u
的最短路径条数为num[u],起点到自身的路径只有一条,所以num[s] = 1,其余
一开始都记为0,这样就可以在d[u] + len < d[v] (即可以使s到v的最短距离
d[v]更优)时,更新d[v]并且让num[v]继承num[u],而当d[u] + len == d[v]
(即有其他最短路径时),将num[u]加到num[v]上。
//增加num[]
int num[N];
void Dijstra(int s){
	num[s] = 0;
	//省略上面模板的重复内容
	……
	//优化
	if(dis[u] + len < dis[v]){
		dis[v] = dis[u] + len;
		num[v] = num[u];
	}else if(dis[u] + len == dis[v]){
		num[v] += num[u];
	}
}

2、新增点权或边权:

以点权为例:用weight[u]表示每个点含有的权,并增加一个w[u],表示从起点到
顶点u所能获得的所有点权,初始时,将w[s] = weight[s],这样就可以在
d[u] + len < d[v] (即可以使s到v的最短距离d[v]更优)时,更新d[v]并且让
w[v] = w[u] + weight[v],而当d[u] + len == d[v](即有其他最短路径时),
对比w[u] + weight[v] 是否大于w[v],若是则更新边权。
//增加weight[],w[]
int weight[N];
int w[N];
void Dijstra(int s){
	w[s] = weight[s];
	//省略上面模板的重复内容
	……
	//优化
	if(dis[u] + len < dis[v]){
		dis[v] = dis[u] + len;
		w[v] = w[u] + weight[v];
	}else if(dis[u] + len == dis[v]){
		if(w[u] + weight[v] > w[v]){
			w[v] = w[u] + weight[v];
		}
	}
}

3、记录最优路径

设置一个数组pre[N];用来记录路径上每个顶点的上一个顶点。之后从终点DFS遍历
一圈就可以得到最优路径。
//e为终点
void dfsPath(int e){
	if(e!=s){
		dfsPath(path[e]);
		printf("%d ", e);
	}else{
		//当找到起点时,结束遍历
		printf("%d", e);
		return;
	}
}

掌握以上知识就可以解决该问题了

满分代码:

#include <bits/stdc++.h>
using namespace std;
int n, m, s, d;
typedef struct
{
    int val;
    int len;
} Node;
const int N = 510;
const int INF = 0x3f3f3f3f;
vector<Node> V[N];
int weight[N];
int num[N];
int w[N];
int dis[N];
bool vis[N];
int pre[N];
void Dijkstra(int s)
{
    num[s] = 1;
    dis[s] = 0;
    w[s] = weight[s];
    for (int i = 0; i < n; i++)
    {
        int u = -1;
        int min = INF;
        for(int j = 0; j < n; j++){
            if(!vis[j] && dis[j] < min){
                u = j;
                min = dis[j];
            }
        }
        if(u == -1)return;
        vis[u] = true;
        
        for(int j = 0; j < V[u].size(); j++){
            int v = V[u][j].val;
            int len = V[u][j].len;
            if(!vis[v] && dis[u] + len < dis[v]){
                num[v] = num[u];
                w[v] = weight[v] + w[u];
                dis[v] = dis[u] + len;
                pre[v] = u;
            }else if(!vis[v] && dis[u] + len == dis[v]){
                num[v] += num[u];
                if(weight[v] + w[u] > w[v]){
                    w[v] = weight[v] + w[u];
                    pre[v] = u;
                }
            }
        }
    }
}
void dfs(int e){
    if(e == s){
        printf("%d",e);
        return;
    }else{
        dfs(pre[e]);
        printf(" %d",e);
    }
}
int main(void)
{
    cin >> n >> m >> s >> d;
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &weight[i]);
    }
    while (m--)
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        Node pb = {b, c};
        Node pa = {a, c};
        V[a].push_back(pb);
        V[b].push_back(pa);
    }
    fill(dis, dis + N, 0x3f3f3f3f);
    Dijkstra(s);
    printf("%d %d\n",num[d],w[d]);
    dfs(d);
    cout << endl;
    return 0;
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值