题目描述
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入格式
输入包括多个数据集合。
每个数据集合的第一行是两个整数 WW 和 HH,分别表示 xx 方向和 yy 方向瓷砖的数量。
在接下来的 HH 行中,每行包括 WW 个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。
输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
数据范围
1≤W,H≤20
输入样例
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0
输出样例
45
解决问题
解题方法
洪水灌溉算法是适用于针对于网格图,方便处理连通块的算法,就是从一点开始搜索。算法复杂度为log(mn),与网格长宽有关。有bfs和dfs两种搜索顺序。bfs搜索为一层一层的搜索扩展,可以求最短距离,dfs搜索不用队列实现,代码更短。
解题思路
首先需要使用while循环来实现多组数据输入,然后循环内使用char数组存入题目给出的地图数据,边输入边寻找起点,找到起点后存储一个起点的下标。然后从起点进入搜索阶段。
BFS
灌溉使用BFS实现洪水灌,特点是利用队列。使用pair<int,int>
来存储一个点的下标每组数据搜索之前要将之前搜过的标记清空,然后将起点标记为已经搜过,接着将起点设为队首。
当队列非空时,先取出队首,然后在上下左右搜索,搜索到未被搜过且在范围内的黑色瓷砖就进行标记,答案加一,同时接入队列。
最后输出答案即可。
流程伪代码
本图取自Acwing本题yxc的视频题解
注意点
- 有关二维数组的困惑
举例:C/C++中我的理解
数组a[4]意思是a[列]
数组b[2][3]是一个两行三列的矩阵,意思为b[行][列]
一开始做题时由于对二维数组定义理解错误在
- 存储x和y坐标时
- 判断是否在范围内时
两部分代码实现时出现了x与y混淆。
以后一维数组x按列数理解二维数组按x为行数,y为列数理解(目前没想到更好的理解😖)
- 有关取出队头与入队的时机的困惑
- 取出队头是在每层扩展之前就开始
- 入队必须是搜索到符合要求的陆地之后才开始
这样做的原因下面举例画图说明:
- 标记数组可以让每个陆地只遍历一遍,入队出队一遍
详细模拟
BFS画图模拟红色不能走,黑色能走且未标记的点,绿色为当前队列中的点,黄色为已经出队并标记的点
实现代码
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define rep(i,m,n) for(int i=m;i<=n;i++)
#define per(i,m,n) for(int i=m;i>=n;i--)
#define PII pair<int,int>
#define endl "\n"
#define int long long
char f[25][25];
bool b[25][25];
int w, h;
int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 };
int bfs(PII bg) {
//从起点开始BFS
int ans = 1;
memset(b, false, sizeof(b));
b[bg.first][bg.second] = true;
queue<PII> q;
q.push(bg);
while (!q.empty()) {
PII pt = q.front();
q.pop();
rep(i, 0, 3) {
int nx = pt.first + dx[i], ny = pt.second + dy[i];
if (nx <= h && nx >= 1 && ny <= w && ny >= 1 && !b[nx][ny] && f[nx][ny] == '.') {
ans++;
b[nx][ny] = true;
q.push({nx,ny});
}
}
}
return ans;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0);
while (cin >> w >> h && (w != 0 && h != 0)) {
//循环输入
PII bg;
rep(i, 1, h) {
rep(j, 1, w) {
cin >> f[i][j];
//初始起点
//二维数组中[i]是行数,[j]是列数
//如a[3]是长度为3的一行,只有列
//a[4][5]则是4行5列
if (f[i][j] == '@') bg.first = i,bg.second = j;
}
}
cout << bfs(bg) << endl;
}
return 0;
}
DFS
使用DFS实现洪水漫灌,无需使用pair<int,int>
,利用函数的形参x,y即可。每组数据搜索之前要将之前搜过的标记清空,然后将起点标记为已经搜过,接着将起点设为队首。
流程伪代码
本图取自Acwing本题yxc的视频题解
注意点
- 边搜索边处理,无需标记数组即可保证每个点只搜索一次
- dfs函数在搜索递归过程中要将ans累加起来
详细模拟
dfs画图模拟红色不能走,黑色能走。ans为每次递归dfs函数的局部变量,最后输出的仅为dfs(起点)的那一个ans值
搜索顺序假定为上右下左
实现代码
#include<iostream>
#include<cstring>
using namespace std;
#define rep(i,m,n) for(int i=m;i<=n;i++)
#define per(i,m,n) for(int i=m;i>=n;i--)
#define PII pair<int,int>
//#define x first
//#define y second
#define endl "\n"
#define int long long
char f[25][25];
int w, h;
int sx, sy;
int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 };//上右下左
int dfs(int x, int y) {
int ans = 1;
//用涂色思想,搜索到就处理,保证只遍历一次,不需要标记数组
f[x][y] = '#';
rep(i, 0, 3) {
int nx = x + dx[i], ny = y + dy[i];
if (nx <= h && nx >= 1 && ny <= w && ny >= 1 && f[nx][ny] == '.') {
//将邻近搜到的相加
ans += dfs(nx, ny);
}
}
return ans;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0);
while (cin >> w >> h && (w != 0 && h != 0)) {
//循环输入
rep(i, 1, h) {
rep(j, 1, w) {
cin >> f[i][j];
//第i行第j列
//初始起点
//sx为行数,sy为列数
if (f[i][j] == '@') sx = i, sy = j;
}
}
cout << dfs(sx, sy) << endl;
}
return 0;
}