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;,这个目的是防止重复标记,不过对于数据比较少的题目这个没有多大的影响,可有可无。

原文地址:https://www.cnblogs.com/happy-MEdge/p/10544533.html