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
题目详解
奶牛上有两个斑点,每一个是一个上下左右四连通的连通块
需要求一下这两个斑点之间最近的距离是多少
距离指的是中间沿着最短路走的步数
- 需要先把这两个块求出来
- 先用枚举的方式从前到后开始枚举,
- 当枚举到x的时候,就可以用深度优先算法,把整个连通块都搜出来
- 搜完之后,再继续枚举,找到没有遍历过的x
遍历连通块有两种方式
1. BFS:宽度优先遍历
2. DFS:深度优先遍历,优势是代码比BFS短,不需要定义队列
3. 并查集
BFS的优势在于可以求最短路
如果默认栈空间比较小,DFS有可能会爆栈,现在一般不需要考虑递归爆栈的问题
时间复杂度
O
(
n
m
)
O(nm)
O(nm)
- 如何求最短距离
从左边的块任意取一个点,从右边的块任意取一个点,求它们之间的最短距离,在所有点对最短距离里面取一个最小值
任取两个点
两个点之间的距离,曼哈顿距离,可以认为它们可以沿着横线直接走,先横着走,再竖着走,可以用这个距离来当作两个点之间的最短距离
从左边任取一个点,从右边任取一个点,两两配对,两两求一下它们之间的曼哈顿距离,也就是两个点坐标之差的绝对值的和
在所有点对之间的距离当中,取一个最小值就一定是答案
为什么正确,证明:
取距离最小值的这个点对,可以证明这条路径一定是存在的,中间一定不会有障碍物,中间一定不会经过其他的x
假设这条路径上有一些其他的块的话,点对的最短路径的起点就可以变更到路径上的其他的块的位置,距离会更短,就和之前的点对是最近的两个点矛盾了
其他的块是左边的块还是右边的块都同理
因此中间的这个路径上一定是没有其他障碍物的,这个路径是一定存在的,而且不存在更短的路径了
所以两个点之间的路径长度可以直接算
数据量大概是 1 0 6 10^6 106的级别
因为数据范围比较小,可以暴力枚举
一般双端队列BFS,比较复杂
可以把这个问题看作是图上的问题
每个格子就是一个点,这个格子和上下左右四个格子相连,就认为它们之间有边
找到任意一个差之后,差跟差之间的距离,认为都是0,因为都在一个连通块内
差跟点之间的距离,或点跟点之间的距离,都是1,因为每走一步相当于是多走到一个点上
这样只需要,任意枚举出来一个差,然后求一下这个差对于其他所有点的最短距离
求完之后,再枚举一下所有的差
- 距离是0的,跟起点差一定在一个块里,因为块内的相对距离都是0
- 距离不是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;
}