【AcWing】蓝桥杯集训每日一题Day11|DFS|最短距离|曼哈顿距离|2060.奶牛选美(C++)

2060.奶牛选美
2060. 奶牛选美 - AcWing题库
难度:中等
时/空限制:1s / 64MB
总通过数:7827
总尝试数:13165
来源:
USACO 2011 November Contest Bronze Division
算法标签
Flood FillBFSDFS

题目内容

听说最近两斑点的奶牛最受欢迎,约翰立即购进了一批两斑点牛。
不幸的是,时尚潮流往往变化很快,当前最受欢迎的牛变成了一斑点牛。
约翰希望通过给每头奶牛涂色,使得它们身上的两个斑点能够合为一个斑点,让它们能够更加时尚。
牛皮可用一个 N×M 的字符矩阵来表示,如下所示:

................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....

其中,X 表示斑点部分。
如果两个 X 在垂直或水平方向上相邻(对角相邻不算在内),则它们属于同一个斑点,由此看出上图中恰好有两个斑点。
约翰牛群里所有的牛都有两个斑点
约翰希望通过使用油漆给奶牛尽可能少的区域内涂色,将两个斑点合为一个。
在上面的例子中,他只需要给三个 . 区域内涂色即可(新涂色区域用 ∗ 表示):

................
..XXXX....XXX...
...XXXX*...XX...
.XXXX..**..XXX..
........XXXXX...
.........XXX....

请帮助约翰确定,为了使两个斑点合为一个,他需要涂色区域的最少数量。

输入格式

第一行包含两个整数 N 和 M。
接下来 N 行,每行包含一个长度为 M 的由 X 和 . 构成的字符串,用来表示描述牛皮图案的字符矩阵。

输出格式

输出需要涂色区域的最少数量。

数据范围

1≤N,M≤50

输入样例:
6 16
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
输出样例:
3
题目详解

奶牛上有两个斑点,每一个是一个上下左右四连通的连通块
需要求一下这两个斑点之间最近的距离是多少
距离指的是中间沿着最短路走的步数

  1. 需要先把这两个块求出来
    1. 先用枚举的方式从前到后开始枚举,
    2. 当枚举到x的时候,就可以用深度优先算法,把整个连通块都搜出来
    3. 搜完之后,再继续枚举,找到没有遍历过的x

遍历连通块有两种方式
1. BFS:宽度优先遍历
2. DFS:深度优先遍历,优势是代码比BFS短,不需要定义队列
3. 并查集
BFS的优势在于可以求最短路
如果默认栈空间比较小,DFS有可能会爆栈,现在一般不需要考虑递归爆栈的问题
时间复杂度 O ( n m ) O(nm) O(nm)

  1. 如何求最短距离
    从左边的块任意取一个点,从右边的块任意取一个点,求它们之间的最短距离,在所有点对最短距离里面取一个最小值
    任取两个点
    两个点之间的距离,曼哈顿距离,可以认为它们可以沿着横线直接走,先横着走,再竖着走,可以用这个距离来当作两个点之间的最短距离
    从左边任取一个点,从右边任取一个点,两两配对,两两求一下它们之间的曼哈顿距离,也就是两个点坐标之差的绝对值的和
    在所有点对之间的距离当中,取一个最小值就一定是答案

为什么正确,证明:
取距离最小值的这个点对,可以证明这条路径一定是存在的,中间一定不会有障碍物,中间一定不会经过其他的x
假设这条路径上有一些其他的块的话,点对的最短路径的起点就可以变更到路径上的其他的块的位置,距离会更短,就和之前的点对是最近的两个点矛盾了
其他的块是左边的块还是右边的块都同理
因此中间的这个路径上一定是没有其他障碍物的,这个路径是一定存在的,而且不存在更短的路径了
所以两个点之间的路径长度可以直接算

数据量大概是 1 0 6 10^6 106的级别

因为数据范围比较小,可以暴力枚举
一般双端队列BFS,比较复杂

可以把这个问题看作是图上的问题

每个格子就是一个点,这个格子和上下左右四个格子相连,就认为它们之间有边
找到任意一个差之后,差跟差之间的距离,认为都是0,因为都在一个连通块内
差跟点之间的距离,或点跟点之间的距离,都是1,因为每走一步相当于是多走到一个点上

这样只需要,任意枚举出来一个差,然后求一下这个差对于其他所有点的最短距离
求完之后,再枚举一下所有的差

  1. 距离是0的,跟起点差一定在一个块里,因为块内的相对距离都是0
  2. 距离不是0的,这个差,就是另外一个块,到当前块的最短距离

这样的话就是一个图论问题,有一个起点,然后枚举一下终点,边权的话不是0就是1,这样就可以用双端队列BFS

双端队列BFS原理

可以看成是Dijkstra算法,因为队列当中只有两种值,在去搜索的时候,就不需要再用堆了,不用用优先队列了,可以用一个普通的双端队列来做
所以双端队列BFS是一个特殊情况的Dijkstra算法

可以把时间复杂度做到 O ( n m ) O(nm) O(nm)

#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 55;

int n, m;
char g[N][N];   //用g数组存整个矩阵
vector<PII> points[2];    //用vector存所有的点

//偏移量方法,把四个方向的偏移量存到一个数组里,方便枚举
//下右上左的顺序
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

//ab表示当前的坐标,vector表示当前存到哪一个points里边
//加一个引用,不加的话每次传vector都会复制一遍,会增加时间复杂度
void dfs(int a, int b, vector<PII>& q)
{
	//先把当前的坐标加到vector当中
	q.push_back({a, b});
	//标记一下格子已经被搜过了
	g[a][b] = '.';
	//枚举一下上右下左四个方向
	for (int i = 0; i < 4; i ++)
	{
		//用xy表示当前方向的坐标
		int x = a + dx[i], y = b + dy[i];
		//判断一下如果发现当前的坐标没有越界,并且下一个格子是X的话
		if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 'X')
			dfs(x, y, q);
	}
	
}

int main()
{
	scanf("%d%d", &n, &m);
	//读入整个矩阵
	for (int i = 0; i < n; i ++)
		scanf("%s", g[i]);

	int k = 0;   //用k来表示当前处理到哪个连通块
	for (int i = 0; i < n; i ++)
		for (int j = 0; j < m; j ++)
			//如果当前位置是X的话
			if (g[i][j] == 'X')
				//遍历一下,把相邻的所有的X找出来
				dfs(i, j, points[k ++]);
	//枚举完之后,定义一下答案
	int res = 110;
	//枚举一下,从第一个点集中任取一个点
	for (auto& a: points[0])
		//从第二个点集中任取一个点
		for (auto& b: points[1])
			//更新一下最短距离
			res = min(res, abs(a.x - b.x) + abs(a.y - b.y));

	//输出答案
	printf("%d\n", res - 1);
	//因为输出的是两个格子之间的块的数量,块的数量=步数-1
	return 0;
}
  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值