CCCC团体程序设计天梯赛 L2-001 紧急救援

记录天梯赛题目合集 跳转查看

题目链接

题目描述

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

输入格式:
输入第一行给出 \(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

题目解析

题目一共三个要求:

  • 求最短路的数目
  • 求最短路下的最大点权和
  • 求该最大点权和的路径节点编号

首先,在解决题目的要求前,要解决的是求最短路。这个题面是明显的单源最短路问题,自然选用 \(dijkstra\) 算法来求解。

明确算法后,要求最短路的数目,就是到达终点的最短距离的个数,那么我们只需要在求最短距离的基础上增加一个记录该距离的个数。
具体地,当到达该点的最短距离变短时,同时更新该点的最短距离数目为上一个点的最短距离数目;当到达该点的最短距离相等时,更新该点的最短距离数目加上上一个点的最短距离数目。

接着是如何解决最短路下的最大点权和,我们知道:最短距离是最小边权和,和求最大点权和是异曲同工的,所以,我们可以和求最短距离一样求最大点权和。但限制是,求最短距离的优先级更高,当最短距离变短时最大点权和要同时更新,当最短距离不变时最大点权和要更新最大。

最后一个问题,如何解决记录最大点权和的路径节点编号,明显的,当到达某个点的最大点权和得到更新我们就可以记录该点的前序节点是什么,最后从终点遍历前序节点到达起点获得路径上的每个节点编号。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <set>

using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

const ll inf = 1e18;
const int INF = 0x3f3f3f3f;
const int N = 500 + 10;

int n, m, s, d;
vector<PII> g[N]; // 建图
int a[N], p[N], c[N], sa[N]; // a记录点权,p记录每个点的前序节点编号,c记录到达节点最短距离个数,sa记录到达节点最大点权和

void dijkstra()
{
	vector<int> dis(n, INF); // 记录最短距离
	vector<bool> st(n, 0); // 记录该节点是否已更新过最短距离
	priority_queue<PII, vector<PII>, greater<PII>> pq;
	pq.push({0, s});
	dis[s] = 0;
	c[s] = 1; // 起点最短距离个数是1
	sa[s] = a[s]; // 起点点权和就是该点的点权
	
	while (!pq.empty()) {
		auto t = pq.top();
		pq.pop();
		
		int u = t.second, dd = t.first;
		if (st[u]) continue;
		st[u] = 1;
		
		for (auto it : g[u]) {
			int v = it.first, w = it.second;
			if (dis[v] > dd + w) {
				dis[v] = dd + w;
				p[v] = u; // 记录该点的前序节点
				c[v] = c[u]; // 更新最短距离个数为上一个点的最短距离个数
				sa[v] = sa[u] + a[v]; // 更新最大点权和
				pq.push({dd + w, v});
			}
			else if (dis[v] == dd + w) {
				c[v] += c[u]; // 更新最短距离个数加上上一个点的最短距离个数
				if (sa[v] < sa[u] + a[v]) { // 如果最大点权和可以进行更新
					p[v] = u; // 记录该点的前序节点
					sa[v] = sa[u] + a[v];
				}
			}
		}
	}
}

void solve()
{
	cin >> n >> m >> s >> d;
	for (int i = 0; i < n; i ++) cin >> a[i], p[i] = i; // 初始化每个点的前序节点是自己
	while (m --) {
		int u, v, w;
		cin >> u >> v >> w;
		g[u].push_back({v, w});
		g[v].push_back({u, w});
	}
	
	dijkstra();
	
	cout << c[d] << ' ' << sa[d] << '\n';
	
	vector<int> res; // 记录路径
	while (d != s) res.emplace_back(d), d = p[d]; // 从终点向前遍历到起点
	res.emplace_back(d);
	reverse(res.begin(), res.end()); // 因为记录的是从终点到起点的,这里进行翻转,也可以不翻转而选择从后向前输出也行
	
	for (int i = 0; i < res.size(); i ++) {
		if (i) cout << ' ';
		cout << res[i];
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	
	int T = 1;
//	cin >> T;
	while (T --) solve();
	return 0;
}
posted @ 2025-03-24 20:48  Natural_TLP  阅读(61)  评论(0)    收藏  举报