题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N \times M\) 的网格,
W表示水,·表示干地。八连通的水格子构成一个水塘,求水塘数量。本质上是连通分量计数问题:用 BFS 或 DFS 遍历每个未访问的水格子,标记整个连通块。 -
算法选择:
- BFS / DFS:遍历网格,遇到未访问的
W时启动 BFS/DFS,标记整个八连通水塘 - 八连通:每个格子有 \(8\) 个邻居(上下左右 + 四个对角线)
- BFS / DFS:遍历网格,遇到未访问的
-
关键步骤:
- 读取数据:读入 \(N, M\) 和网格
- 遍历网格(\(i = 1\) 到 \(N\),\(j = 1\) 到 \(M\)):
- 若 \(a[i][j] = 'W'\) 且未访问:
- \(ans \leftarrow ans + 1\)
- BFS/DFS 标记整个八连通水塘
- 若 \(a[i][j] = 'W'\) 且未访问:
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot M)\),每个格子最多访问一次
- 空间复杂度:\(O(N \cdot M)\),访问标记数组和队列/递归栈
-
连通分量计数的核心思想:
- 八连通的定义:水塘不仅包括上下左右相邻,还包括对角线相邻,因此有 \(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
浙公网安备 33010602011771号