POJ 2386 Lake Counting
传送门
题面:
Due to recent rains, water has pooled in various places in Farmer John's field, which is represented by a rectangle of N x M (1 <= N <= 100; 1 <= M <= 100) squares. Each square contains either water ('W') or dry land ('.'). Farmer John would like to figure out how many ponds have formed in his field. A pond is a connected set of squares with water in them, where a square is considered adjacent to all eight of its neighbors.
Given a diagram of Farmer John's field, determine how many ponds he has.
Input
-
Line 1: Two space-separated integers: N and M
-
Lines 2..N+1: M characters per line representing one row of Farmer John's field. Each character is either 'W' or '.'. The characters do not have spaces between them.
Output
- Line 1: The number of ponds in Farmer John's field.
Sample Input
10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.(样例建议看原题)
Sample Output
3
Hint
OUTPUT DETAILS:
There are three ponds: one in the upper left, one in the lower left,and one along the right side.
题面描述
农场下雨了,然后想知道农场有多少个水坑,这里用'W'表示这块地有水,判断是否为同一个水坑的标志是水连在一起,也就是:如果一个水的相邻方向(上下左右和对角)都有水的话,那么它们就属于同一个水坑。具体可以看样例。
题目分析
呃,这是一道经典的用dfs求连通块的题。什么是连通块?这里的水坑就是一个连通块。那么,我们如何用dfs,即递归的方法完成这道题呢?
我们先看看AC代码:
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 100+5;
char a[maxn][maxn];
int n, m;
bool check(int r, int c){ //越界检查
if(r < 0 || r >= n || c < 0 || c >= m) return false;
return true;
}
void dfs(int r, int c){
if(a[r][c] == '#') return; //被标记过就不用继续标记了
a[r][c] = '#'; //标记
int r1, c1;
for(r1 = -1; r1 <= 1; r1++) //遍历上下左右对角(九宫格),这里不会遍历到自己
for(c1 = -1; c1 <= 1; c1++)
if(check(r+r1, c+c1) && a[r+r1][c+c1] == 'W')
dfs(r+r1, c+c1); //找到相邻的'W'继续进行遍历
//遍历完相邻的'W'后回溯,这里return省略不写
}
int main(){
memset(a, 0, sizeof(a)); //清零
cin >> n >> m;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
cin >> a[i][j];
int cnt = 0;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
if(a[i][j] == 'W') {dfs(i, j); cnt++;} //找到一个水而且没被标记过,就进行深度搜索,计数
cout << cnt << endl;
return 0;
}
这里涉及到一个新的方法:种子填充,我先摆在这里,后面再讲。下面是维基百科演示种子填充的gif图:
我们一步步分析代码怎么得来,先看主函数做了什么:
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
if(a[i][j] == 'W') {dfs(i, j); cnt++;}
这里的思路就是,当我找到一个水后,把相邻的水连通起来,然后计数:
那么,我们“连通水”这个函数怎么写呢?这里就要用到我们之前说的方法,种子填充。其实通俗点理解就是做标记。就像这样:
为什么要做标记呢?如果不做标记,主函数就会继续遍历到同一个水坑。所以,我们的函数就实现两个功能:一:做标记,二:遍历相邻的水然后连通它们。而且我们也注意到,当遍历到其中一个相邻的水时,这个相邻的水又可以做为中心点去遍历其他的水,从而达到连通的目的。这提示了我们可以用递归来解决这个重复做的事,所以大概代码是这样:
void dfs(int r, int c){
a[r][c] = '#'; //标记自己本身
int r1, c1;
for(r1 = -1; r1 <= 1; r1++) //遍历相邻的水
for(c1 = -1; c1 <= 1; c1++)
if(a[r+r1][c+c1] == 'W')
dfs(r+r1, c+c1);
//遍历完后自动回溯
}
到了这里,好像还少了些什么?没错,可能会出现数组越界访问的问题,所以只需要这样:
void dfs(int r, int c){
a[r][c] = '#';
int r1, c1;
for(r1 = -1; r1 <= 1; r1++)
for(c1 = -1; c1 <= 1; c1++)
if(r+r1 >= 0 && r+r1 < n && c+c1 >= 0 && c+c1 < m && a[r+r1][c+c1] == 'W')
dfs(r+r1, c+c1);
}
因为我忍受不了一行这么长的代码,所以 我用了一个check()
函数解决这个问题。
然后这道题就解决了。
有人会问,我的dfs函数的代码还多了个if(a[r][c] == '#') return;
,这个目的是防止重复标记,不过对于数据比较少的题目这个没有多大的影响,可有可无。