【AcWing】蓝桥杯集训每日一题Day31|最短路|朴素Dijkstra算法|1375.奶牛回家(C++)

本文讨论了如何使用Dijkstra算法解决奶牛回家问题,将实际问题转化为图论中的单源最短路径问题,适用于IT技术中的算法教学和编程挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1375.奶牛回家
1375. 奶牛回家 - AcWing题库
难度:简单
时/空限制:1s / 64MB
总通过数:1407
总尝试数:2833
来源:

usaco training 2.4
算法标签

最短路DijkstraSPFA

题目内容

晚餐时间马上就到了,奶牛们还在各自的牧场中悠闲的散着步。
当农夫约翰摇动铃铛,这些牛就要赶回牛棚去吃晚餐。
在吃晚餐之前,所有奶牛都在自己的牧场之中,有些牧场中可能没有奶牛。
每个牧场都通过一条条道路连接到一个或多个其他牧场(可能包括其自身)。
有时,两个(可能是相同的)牧场通过一条以上的道路相连。
至少存在一个牧场与牛棚通过一条道路直接相连。
所有奶牛都能够成功的从自己的牧场沿道路返回牛棚。
聪明的奶牛们总会选择最短的路径回到牛棚之中。
每条道路都是可以双向行走的,奶牛的行走速度也都一样。
我们用 a∼z 和 A∼Y 来标记所有的牧场。
所有用大写字母标记的牧场中都存在一头奶牛,所有用小写字母标记的牧场中都不存在奶牛。
牛棚的标记为 Z,这里最初是没有奶牛的。
现在你需要确定,哪一头奶牛能够最快到达牛棚,输出它最初所在的牧场的标记,并输出它走过的路径的长度。
注意,同一字母大小写标记的两个牧场(例如,牧场 A 和牧场 a)是两个完全不同的牧场。

输入格式

第一行包含整数 P,表示连接牧场以及牛棚的道路的条数。
接下来 P 行,每行包含两个字母以及一个整数,表示被一条道路连接的两个牧场的标记,以及这条道路的长度。

输出格式

输出一个字母和一个整数,表示最快回到牛棚的牛最初所在的牧场的标记以及它走过的路径的长度。
数据保证最快回到牛棚的牛只有一头。

数据范围

1≤P≤10000,
所有道路长度均不超过 1000。

输入样例:
5
A d 6
B d 3
C e 9
d Z 8
e Z 3
输出样例:
B 11
题目解析

如果把牧场看作图中的点,道路看作图种的边的话,图可能是有自环的
两点之间可能会有重边
有可能有自环
没有孤立点,可能有多个连通块
所有奶牛都能返回牛棚
边是双向边
Z是牛棚,大写字母的牧场有1头奶牛,小写字母的牧场没有奶牛

输出A~Y当中的一个字母,最快到达牛棚,输出路径长度

边数不超过10000

图论的问题

单源最短路问题

Z其实是终点,但是因为每条边是双向的,所以每个点到终点的路径反过来看,相当于把Z看成起点,到另外一个点的路径
本来要求的是终点是Z的里面的最短路径,变成以Z为起点,到哪个大写字母最近

只有一个起点,求这个点到其他某些点的最短距离
所以是单源最短路问题

最短路算法有很多个

最短路

  1. 单源最短路
    1. 所有边权都是正数
      1. 朴素Dijkstra算法 O ( n 2 ) O(n^2) O(n2)
      2. 堆优化版的Dijkstra算法 O ( m log ⁡ n ) O(m\log n) O(mlogn)
    2. 存在负权边
      1. Bellman-Ford O ( n m ) O(nm) O(nm)
      2. SPFA 一般 O ( m ) O(m) O(m),最坏 O ( n m ) O(nm) O(nm)
  2. 多源汇最短路
    1. Floyd算法 O ( n 3 ) O(n^3) O(n3)

点数是52,边数是10000,是一个稠密图

代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 60;

int n = 52, m;
int g[N][N], dist[N];
//g来表示两点之间边的长度,dist表示每个点到Z的最短距离
bool st[N];   
//st是朴素版Dijkstra的状态数组,表示每个点的最短路是不是已经确定了

int get(char c)
{
	if (c <= 'Z') return c - 'A' + 1;
	return c - 'a' + 27;
}

void dijkstra()
{
	//初始化每个点到起点的距离
	memset(dist, 0x3f, sizeof dist);
	dist[26] = 0;

	for (int i = 0; i < n - 1; i ++)
	{
		//t表示当前没有确定最短距离的点当中,距离对短的一个点的编号
		int t = -1;
		for (int j = 1; j <= n; j ++)
			//如果发现当前点的最短距离没有被确定
			if (!st[j] && (t == -1 || dist[j] < dist[t]))
				//更新一下当前最短距离的点的编号
				t = j;
		st[t] = true;
		for (int j = 1; j <= n; j ++)
			dist[j] = min(dist[j], dist[t] + g[t][j]);
	}
}

int main()
{
	//读入边数
	cin >> m;
	//将所有点之间的距离初始化为正无穷
	memset(g, 0x3f, sizeof g);

	while (m --)
	{
		char a, b;
		int c;
		cin >> a >> b >> c;
		//实现一个辅助函数,可以将每个字母转化成一个数字编号
		int x = get(a), y = get(b);
		//更新一下距离
		//因为是无向边,两个方案都写一下,可能有重边,取一个min
		g[x][y] = g[y][x] = min(g[x][y], c);
	}
	
	dijkstra();

	int res = 1;
	for (int i = 2; i <= 25; i ++)
		if (dist[i] < dist[res])
			res = i;

	cout << (char)(res - 1 + 'A') << ' ' << dist[res] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值