题解:洛谷 P1596 Lake Counting

【题目来源】

洛谷:P1596 [USACO10OCT] Lake Counting S - 洛谷

【题目描述】

由于最近的降雨,水在农夫约翰的田地里积聚了。田地可以表示为一个 \(N \times M\) 的矩形(\(1 \leq N \leq 100\)\(1 \leq M \leq 100\))。每个方格中要么是水(W),要么是干地(.)。农夫约翰想要弄清楚他的田地里形成了多少个水塘。一个水塘是由连通的水方格组成的,其中一个方格被认为与它的八个邻居相邻。给定农夫约翰田地的示意图,确定他有多少个水塘。

【输入】

\(1\) 行:两个用空格分隔的整数:\(N\)\(M\)

\(2\) 行到第 \(N+1\) 行:每行 \(M\) 个字符,表示农夫约翰田地的一行。

每个字符要么是 W,要么是 .

字符之间没有空格。

【输出】

\(1\) 行:农夫约翰田地中的水塘数量。

【输入样例】

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

【输出样例】

3

【核心思想】

  1. 问题分析:给定 \(N \times M\) 的网格,W 表示水,· 表示干地。八连通的水格子构成一个水塘,求水塘数量。本质上是连通分量计数问题:用 BFS 或 DFS 遍历每个未访问的水格子,标记整个连通块。

  2. 算法选择

    • BFS / DFS:遍历网格,遇到未访问的 W 时启动 BFS/DFS,标记整个八连通水塘
    • 八连通:每个格子有 \(8\) 个邻居(上下左右 + 四个对角线)
  3. 关键步骤

    • 读取数据:读入 \(N, M\) 和网格
    • 遍历网格\(i = 1\)\(N\)\(j = 1\)\(M\)):
      • \(a[i][j] = 'W'\) 且未访问:
        • \(ans \leftarrow ans + 1\)
        • BFS/DFS 标记整个八连通水塘
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot M)\),每个格子最多访问一次
    • 空间复杂度:\(O(N \cdot M)\),访问标记数组和队列/递归栈
  5. 连通分量计数的核心思想

    • 八连通的定义:水塘不仅包括上下左右相邻,还包括对角线相邻,因此有 \(8\) 个方向
    • BFS 与 DFS 的等价性:两种方法均可求解,BFS 用队列显式维护,DFS 用递归隐式维护
    • 访问标记的重要性:标记已访问的水格子避免重复计数,同时防止无限循环
    • DFS 的破坏性写法:第二种实现将访问过的 W 直接改为 .,省去额外的 vis 数组,空间更优
    • 适用于连通区域计数、洪水填充、网格遍历类问题

【解题思路】

【算法标签】

普及- #BFS-二维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, m, ans=0, vis[105][105]={0};
int dx[8]={-1,-1,-1,0,0,1,1,1}, dy[8]={-1,0,1,-1,1,-1,0,1};  // 定义8个方向的偏移坐标
char a[105][105];
struct node {
    int x, y;
};
int main()
{
    cin >> n >> m;  // 输入n和m
    for (int i=1; i<=n; i++) {  // 双重for循环遍历矩阵
        for (int j=1; j<=m; j++) {
            cin >> a[i][j];  // 输入每个位置的字符,W或.
        }
    }
    for (int i=1; i<=n; i++) {  // 双重for循环遍历矩阵
        for (int j=1; j<=m; j++) {
            if (a[i][j]=='W' && vis[i][j]==0) {  // 当遇到水坑'W',且没有访问过时
                ans++;  // 水坑数量自增1
                queue<node> q;  // 定义队列
                node tp = {i, j};  // 定义tp节点,此处用结构体来记录x和y坐标
                q.push(tp);  // 压入队列中
                vis[i][j]=1;  // 并标记该位置已来过
                while (!q.empty()) {  // 当队列不为空
                    node tp = q.front();  // 取出队头
                    q.pop();
                    for (int k=0; k<8; k++) {  // 遍历8个方向
                        int xx = tp.x + dx[k];  // 得到移动后的x坐标
                        int yy = tp.y + dy[k];  // 得到移动后的y坐标
                        if (xx<1 || yy<1) continue;  // 进行边界判断,超边界的继续循环
                        if (a[xx][yy]=='W' && vis[xx][yy]==0) {  // 如果移动后仍为水坑'W',且没有访问过
                            node t = {xx,yy};  // 定义t
                            q.push(t);  // 将t压入队列中
                            vis[xx][yy]=1;  // 并标记该位置已来过
                        }
                    }
                }
            }
        }
    }
    cout << ans << endl;  // 最后输出水坑数量
    return 0;
}
// 使用dfs再写一遍
#include <bits/stdc++.h>
using namespace std;
int n, m, ans;
char g[105][105];
int dx[8] = {-1,-1,-1,0,1,1,1,0};
int dy[8] = {-1,0,1,1,1,0,-1,-1};
void dfs(int x, int y)
{
    g[x][y] = '.';
    for (int i=0; i<8; i++) {
        int xx = x + dx[i];
        int yy = y + dy[i];
        if (xx<0 || xx>=n || yy<0 || yy>=m || g[xx][yy]=='.') continue;
        dfs(xx, yy);
    }
}
int main()
{
    cin >> n >> m;
    for (int i=0; i<n; i++)
        for (int j=0; j<m; j++)
            cin >> g[i][j];
    for (int i=0; i<n; i++)
        for (int j=0; j<m; j++) {
            if (g[i][j]=='W') {
                ans++;
                dfs(i, j);
            }
        }
    cout << ans << endl;
    return 0;
}

【运行结果】

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
3
posted @ 2026-08-17 09:45  团爸讲算法  阅读(25)  评论(0)    收藏  举报