天梯赛l2-001紧急救援(堆优化的Dijkstra算法,25分代码)

l2-001紧急救援

334202395cd345d3874ce7135f47ab5d.png

前言

这道题给我爽到了,前置知识:堆优化的Dijkstra算法,链式前向星。

题目

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

输入格式:
输入第一行给出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    时间限制    200 ms    内存限制     64 MB

思路

首先注意审题

第一行输出的最短路径条数->距离相同的最短路条数,注意这里不是最短路长度。

其他的就是一个堆优化的Dijkstra算法,详见代码,不懂的可以问我。

下面的代码注释写得有点多,可以复制下来再看。

AC代码

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int N = 250000;//这里必须是这个值以上,不然最后一个测试点就会段错误,这是因为城市最大500个,那么路径最多500*499条
struct node//链式前向星,不会的可以学一下
{
	int to, next, dis;
};
struct nd//构建堆所需要的结构体
{
	int pos, dis;//pos是点,dis是从起点s到点pos的距离
	bool operator<(const nd& q)const { return dis > q.dis; }//小根堆,堆顶最小
};
priority_queue<nd, vector<nd>>q;//这里是小根堆
node e[N];//这里必须长度在500*499个以上
int n, m, s, d, cnt, dis[N], sz[N], jl[N], head[N], pre[N];//这里的数组必须长度大于500
long long shuliang[N];//这里也是必须长度大于500,为了方便起见,全部弄到500*500的长度
bool vis[N];
//e[N]是边,dis[p]是从起点s到p点的距离,sz[N]是每个城市救援队数量,jl[p]是从s到p集结的救援队数量;head[u]是链式前向星需要的数组,用于存储最近一次以u为起点的边的编号;pre[p]是用来存储p点的上一个点的;shuliang[p]从起点s到p点的最短路的条数,注意是条数,不是长度,这里很重要;vis[N]是堆优化的Dijkstra算法中用来染色的。
void line(int u, int v, int dis)//链式前向星的加边函数
{
	++cnt;
	e[cnt].next = head[u];
	e[cnt].dis = dis;
	e[cnt].to = v;
	head[u] = cnt;
}
void lu()//堆优化的Dijkstra算法
{
	memset(dis, 0x3f, sizeof dis);//memset是按字节赋值的,这样可以把dis数组每一个元素都赋为1061109567,数量级是1e9,十亿,在这道题中用来模拟无穷大已经够用。
	dis[s] = 0;//起点s距离起点s当然是0了
	jl[s] = sz[s];//从s到s集结的救援队就是s点含有的救援队数量
	shuliang[s] = 1;//从s到s的最短路条数当然是1
	q.push({ s,dis[s] });
	while (!q.empty())//常用bfs,一般都这样写
	{
		int pos = q.top().pos;//这里是点
		q.pop();//出队或者说出堆,销毁这个堆顶元素
		if (vis[pos]) continue;//染色的不管,我只需要没染色的点里面dis最小的点
		vis[pos] = true;//染色
		for (int i = head[pos]; i; i = e[i].next)
		{
			int to = e[i].to;
			//if (vis[to])continue;//这里加了也一样,就当剪枝了(虽然这不是dfs
			if (dis[to] > dis[pos] + e[i].dis)//如果“pos到to的距离”+“s到pos的距离”是小于“s到to的距离”,那么就说明在“已经处理过的情况中” “经由pos到to是最优的”
			{
				dis[to] = dis[pos] + e[i].dis;//将当前最优赋值
				jl[to] = jl[pos] + sz[to];//既然距离上是当前最优,而救援队总数是由路径决定的,那么此时总的救援队数量也要相应更改
				q.push({ to,dis[to] });//这个已经是当前最优了,当然要入堆了
				pre[to] = pos;//存储路径,这里为后面输出路径做准备,因为在函数中路径是一直在变的,所以用一个数组一直跟着变,反正每个to的最后一次赋值一定是to点最终结果,也就是说对于to点,只有pos[to]的最后一次赋值是有意义的
				shuliang[to] = shuliang[pos];//既然原来的dis[to]不是当前最优解,现在的dis[pos]+e[i].to才是当前最优解,那么直接用pos的最短路数量覆盖to的最短路数量,因为原来的to已经不是最短路,直接扔了就行
			}
			else if (dis[to] == dis[pos] + e[i].dis)//这里必须是else哦,如果将此处else if改为if,那么最短路数量就会:pos+e[i].to覆盖to的最短路,然后又乘以二,这显然逻辑上是错的
			{
				shuliang[to] += shuliang[pos];
				if (jl[to] < jl[pos] + sz[to])//如果已经是最短路,那么就比较一下哪条最短路的救援队总数更多
				{
					jl[to] = jl[pos] + sz[to];//显然经由pos到to的最短路更胜一筹,那么此时救援队总数相应改变
					pre[to] = pos;//已经选择了路径,那么存储的路径相应改变
//注意,这里不需要push进去,因为一定有相同的to,dis[to]已经push进去了,加了只能浪费时间
//不过如果执意要加,结果依然不变,因为你只是浪费了时间
				}
			}
		}
	}
}
int main()
{
	cin >> n >> m >> s >> d;
	++s, ++d;//这里需要+1,因为在Dijkstra算法中遍历边时,我选择的条件是i!=0,因此0不应该是点;pre存储路径时,方便起见,0也不应该是点;总的来说,不把0当点会比较好操作
	for (int i = 1; i <= n; ++i)cin >> sz[i];//输入每个点的救援队数量
	while (m--)
	{
		int a = 0, b = 0, c = 0;
		cin >> a >> b >> c;
		++a, ++b;//起点和终点加了1,这里也必须加1
		line(a, b, c), line(b, a, c);//a到b,b到a,两个方向两条边
	}
	lu();
	int i = d;
	vector<int>sum;
	while (i)//准备输出路径
	{
		sum.push_back(i);
		i = pre[i];
	}
	reverse(sum.begin(), sum.end());
	cout << shuliang[d] << " " << jl[d] << endl;
	cout << sum[0] - 1;
	for (int i = 1; i < sum.size(); ++i)cout << " " << sum[i] - 1;
	return 0;
}

这里的pre能不能反过来呢?比如pre[pos]=to?

这是不行的,因为这是一颗树。

请仔细思考。

弄明白了,最短路基本上就是秒。

千里之行,始于足下。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值