Problem Description
There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.
Write a program to count the number of black tiles which he can reach by repeating the moves described above.
-
Input
The input consists of multiple data sets. A data set starts with a line containing two positive integersW
andH
;W
andH
are the numbers of tiles in the x- and y- directions, respectively.W
andH
are not more than 20.There are
H
more lines in the data set, each of which includesW
characters. Each character represents the color of a tile as follows.‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set) -
Output
For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).
Sample Input
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
11 9
.#.........
.#.#######.
.#.#.....#.
.#.#.###.#.
.#.#..@#.#.
.#.#####.#.
.#.......#.
.#########.
...........
11 6
..#..#..#..
..#..#..#..
..#..#..###
..#..#..#@.
..#..#..#..
..#..#..#..
7 7
..#.#..
..#.#..
###.###
...@...
###.###
..#.#..
..#.#..
0 0
Sample Output
45
59
6
13
題意:一個人站在一個長方形的房間裏,瓷磚是方形的,有紅黑兩色,這個人站在黑色瓷磚上,位置以’@‘表示,其他的黑色瓷磚用’.‘表示,紅色瓷磚用’#'表示,人可以上下左右移動到相鄰的黑色瓷磚上。題目要求我們求出這個人從初始位置能夠到達的黑色瓷磚的數量。
思路:暴力搜索整個解空間,用BFS和DFS。可以作爲【搜索技術】的入門題。
當然,可能有點難度的一點,就是圖論的建模,這道題只需把每塊瓷磚視作一個圖的一個頂點就可以了,用每塊瓷磚的座標表示。從起點出發,看起點所在的聯通分量有多少個頂點。
還有一個問題就是,如何從一個點移動到上下左右去?可以用“四連通”的寫法。
Move[][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}
解決方法一:BFS
爲了講得清楚,畫圖,以四行五列的房間爲例。BFS從起點擴散開,像一顆石子丟進池塘,激起的波浪會一層層擴散到整個空間,而且擴散按照從近到遠的順序。用隊列處理這個擴散的過程很清晰:
#include <iostream>
#include <queue>
using namespace std;
const int maxn = 25;
char room[maxn][maxn];
int Move[][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; // 左上右下
#define check(x, y) ((x < w && x >= 0) && (y < h && y >= 0))
int dx, dy;
struct point { int x, y; };
int w, h;
int bfs() {
queue<point> q;
int num = 1; // 記錄可走的黑色瓷磚數量
q.push(point{dx, dy}); // 從起點開始
room[dy][dx] = '#'; // 把起點堵住防止重複計算, 其實可以不寫, 因爲起點是'@'而非'.'
while (!q.empty()) {
point t = q.front(); q.pop();
for (int i = 0; i < 4; ++i) {
int tx = t.x + Move[i][0], ty = t.y + Move[i][1];
if (check(tx, ty) && room[ty][tx] == '.') {
++num; // 可走的路多了一條
q.push(point{tx, ty});
room[ty][tx] = '#'; // 防止回退
}
}
}
return num;
}
int main() {
while (~scanf("%d %d", &w, &h)) {
if (w == 0 && h == 0) break;
for (int i = 0; i < h; ++i) {
for (int j = 0; j < w; ++j) {
cin >> room[i][j];
if (room[i][j] == '@') { //找到起點座標
dx = j;
dy = i;
}
}
}
printf("%d\n", bfs());
}
return 0;
}
解決方法二:DFS+遞歸
也可以用棧模擬,不過這樣就和隊列+BFS的寫法幾乎完全一樣了。過程描述如下:
(1)在初始位置,num = 1,標記這個位置已經被走過;
(2)左、上、右、下4個方向,按照順序選一個能走的方向,走一步;
(3)在新的位置,num++,標記這個位置已經被走過;
(4)繼續上訴過程;
(5)如果無路可走,回退到上一步,換個方向再走;
(6)繼續上訴過程,直到結束。
畫圖如下:
#include <iostream>
#include <cstdio>
#define isLegal(x, y) (x >= 0 && x < w && y >= 0 && y < h)
using namespace std;
const int maxn = 25;
int h, w;
int Move[][4] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}}; //x, y 左上右下
char room[maxn][maxn];
int x, y; //起點的座標
int num = 0; //可以行走的黑色瓷磚的數量
void dfs(int x, int y) {
room[y][x] = '#'; //標記這個位置,表示已經走過了
++num;
for (int i = 0; i < 4; ++i) { //在左上右下四個方向順時針深搜
int nextX = x + Move[i][0], nextY = y + Move[i][1];
if (isLegal(nextX, nextY) && room[nextY][nextX] == '.') //選一個能走的方向
dfs(nextX, nextY);
}
}
int main() {
while (~scanf("%d%d", &w, &h)) { //寬和高 列和行
if (!w && !h) break;
for (int i = 0; i < h; ++i) {
for (int j = 0; j < w; ++j) {
cin >> room[i][j];
if (room[i][j] == '@') x = j, y = i; //找到起點座標
}
}
dfs(x, y);
cout << num << endl;
num = 0;
}
return 0;
}