7-16 城市间紧急救援 (综合最短路练习)

本题链接:PTA | 程序设计类实验辅助教学平台

题目:

样例:

输入
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

思路:

        这道题是经典的综合最短路问题,

综合了 三种最短路方法,1.求路径条数2.最短路多边权问题3.求最短路的路径

这里有个注意的点是,结点人数的的比较应该是更多更好。所以堆排序的时候,注意将结点人数总和多的排在前面。

代码详解如下:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#define endl '\n'
#define x first
#define y second
#define mk make_pair
#define int long long
#define NO puts("NO")
#define YES puts("YES")
#define umap unordered_map
#define INF 0x3f3f3f3f3f3f3f3f
#define All(x) (x).begin(),(x).end()
#pragma GCC optimize(3,"Ofast","inline")
#define ___G std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)
using namespace std;
const int N = 2e6 + 10;
using PII = pair<int,int>;

struct Edge
{
	int a;	// 相关结点
	int dis;	// 相关最短距离	
	int peo;	// 相关人数总和
	
	// 构造相关结构体
	inline Edge(int a,int dis,int peo)
	{
		this->a = a;
		this->dis = dis;
		this->peo = peo;
	}
	
	// 重载 < 比较符,建立堆排序规则
	inline bool operator<(const Edge&t)const
	{
		// 优先排序最短路问题,最短路的排前面
		if(dis != t.dis) return dis > t.dis;
		// 如果最短路相同,优先排序人数多的在前面
		return peo < t.peo;
	}
};

int n,m,k,start,last;

int havePeo[N];	// 记录经过结点的拥有总救援人数
int dist[N];	// 记录最短路距离
int peo[N];		// 记录结点城市拥有的救援人数
bool st[N];		// 标记走动的结点城市
int tree[N];	// 记录结点城市由哪上一个结点城市相连的,求路径
int treeSum[N];	// 记录路径条数
vector<int>path;	// 记录路径

int len,sum;	// sum 是总救援人数,len 是路径结点个数

// 建立链表
int h[N],e[N],ne[N],w[N],idx;
inline void Add(int a,int b,int c)
{
	e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

inline void Dijkstra()
{
	// 初始化最短距离和路径条数和总救援人数
	memset(dist,INF,sizeof dist);
	dist[start] = 0;
	havePeo[start] = 0;
	treeSum[start] = 1;
	
	// 建立堆
	priority_queue<Edge>q;
	
	// 存储起点相关结构体
	q.push(Edge(start,0,0));
	
	// 开始堆排序的 Dijkstra
	while(q.size())
	{
		// 获取优先走动的相关结构体
		Edge now = q.top();
		q.pop();
		
		int a = now.a;	// 获取当前走动的结点
		int dis = now.dis;	// 获取当前记录的最短路距离
		int p = now.peo;	// 获取当前记录的拥有救援总人数
		
		// 如果当前结点走动过,不必再继续更新走动
		if(st[a]) continue;
		st[a] = true;	// 标记当前走动的结点城市
		
		for(int i = h[a];i != -1;i = ne[i])
		{
			int j = e[i];	// 获取走动的结点
			// 如果 j 走动结点的最短路距离方案大于 当前解 a 结点 到 j 结点的距离
			if(dist[j] > dis + w[i])
			{
				dist[j] = dis + w[i];// 更新最短路
				treeSum[j] = treeSum[a];	// 继承路径条数
				tree[j] = a;	// 记录当前 j 结点城市是由 a 结点走动得来的
				havePeo[j] = p + peo[j];	// 记录走动到 j 结点路径拥有的总救援人数
			}else	// 如果最短距离方案 与当前 a 结点走动到 j 结点最短距离相同
			if(dist[j] == dis + w[i])
			{
				// 累计路径条数
				treeSum[j] += treeSum[a];
				
				// 如果当前 j 结点拥有的总救援人数方案 比 a 到 j 结点的总救援人数 少,那么更新路径
				if(havePeo[j] < p + peo[j])
				{
					havePeo[j] = p + peo[j];	// 更新走动到 j 结点路径拥有的总救援人数
					tree[j] = a;	// 更新结点城市路径
				}
			}
			// 存储走动的 j 结点,进行下一次的走动比较
			q.push(Edge(j,dist[j],havePeo[j]));
		}
	}
}

// 获取最短路的路径
void getPath(int now)
{
	// 递归到了起点,开始回溯取路径
	if(now == start)
	{
		// 存储路径
		path.emplace_back(now);
		++len;	// 记录路径点个数
		sum += peo[now];	// 累加救援人数
		return ;
	}
	
	// 递归求路径
	getPath(tree[now]);
	
	// 存储路径
	path.emplace_back(now);
	++len;	// 记录路径点个数
	sum += peo[now];	// 累加救援人数
}

inline void solve()
{
	// 初始化链表
	memset(h,-1,sizeof h);
	
	cin >> n >> k >> start >> last;
	
	for(int i = 0;i < n;++i)
	{
		cin >> peo[i];
	}
	
	while(k--)
	{
		int a,b,c;
		cin >> a >> b >> c;
		
		// 相连两个城市
		Add(a,b,c);
		Add(b,a,c);
	}
	
	// Dijkstra 求值
	Dijkstra();
	
	// 由终点回溯递归获取路径
	getPath(last);
	
	// 输出 路径条数,和 救援人员总人数
	cout << treeSum[last] << ' ' << sum << endl;
	
	// 输出救援路径
	for(int i = 0;i < len;++i)
	{
		if(i) cout << ' ';
		cout << path[i];
	}
}

signed main()
{
//	freopen("a.txt", "r", stdin);
	___G;
	int _t = 1;
//	cin >> _t;
	while (_t--)
	{
		solve();
	}

	return 0;
}

最后提交:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值