[USACO10OCT] Lake Counting S
扫描网格,每遇到未访问水格就用八方向 BFS 淹掉整个连通块并把答案加一。
OJ: luogu
题目 ID: P1596
难度:普及-
标签:BFSflood fill连通块网格
日期: 2026-07-16 18:01
形式化题目
给定一个 W 或干地 .。两个格子若上下左右或斜对角相邻且都是水,则属于同一个水塘;水塘是八方向连通的水格的极大集合。求网格中水塘的数量。
思路
数连通块没有更弱的直接做法:任何正确算法都必须把每个水塘完整遍历一遍,所以洪水填充(Flood Fill)既是朴素想法也是最终做法,不存在"慢暴力 -> 快优化"的演进,这里不单独展示暴力代码。
先看八方向相邻的含义。下面这张 4x4 网格演示一个小样例:
text
初始网格 扫描到 (1,1):水塘数 1,BFS 淹没左上连通块 继续扫描
WW.. **.. **..
W.W. *.*. *.*.
.... .... ....
..W. ..W. ..**观察两点:
- 图中有两个水塘:左上连通块
(1,1),(1,2),(2,1),(2,3)和右下(4,3)。(1,2)与(2,3)只是斜对角接触,八方向下属于同一个水塘;若只算上下左右,它们就会分开。所以方向数组必须包含四条对角线。 - 扫描到
(1,1)时答案加一,BFS 把整个水塘的格子标记掉;之后扫描再遇到(4,3)时它仍然是W,说明它是一个新水塘,答案再加一。扫描时遇到W只可能来自一个还没统计过的水塘,这就是计数正确性的来源。
实现上直接修改网格:BFS 每访问一个水格就把它改成 .,这一步同时完成了访问标记,之后的扫描和扩展都不会再处理它,省掉单独的 vis 数组。入队前标记而不是出队时标记,保证每个格子最多入队一次。
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-27 00:00
* update_at: 2026-08-13 13:33
*/
/* P1596 [USACO10OCT] Lake Counting S */
/* 扫描网格,每遇到未访问水格就用八方向 BFS 淹掉整个连通块并把答案加一。 */
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int n, m;
char g[MAXN][MAXN]; // 网格:'W' 是水,'.' 是干地
int dx[8] = { -1, -1, -1, 0, 0, 1, 1, 1 }; // 八方向:上下左右加四条对角线
int dy[8] = { -1, 0, 1, -1, 1, -1, 0, 1 };
// BFS 从 (sx, sy) 出发,把整个水塘里的 'W' 全部标记成 '.'。
void flood_fill(int sx, int sy) {
queue<pair<int, int>> q;
g[sx][sy] = '.';
q.push(make_pair(sx, sy));
while (!q.empty()) {
int x = q.front().first;
int y = q.front().second;
q.pop();
// 向八个方向扩展,未访问的水格直接标记后入队。
for (int i = 0; i < 8; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && g[nx][ny] == 'W') {
g[nx][ny] = '.'; // 入队前标记,避免同一个格子重复入队
q.push(make_pair(nx, ny));
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> (g[i] + 1);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (g[i][j] == 'W') { // 遇到未访问的水格,必然是一个新水塘
ans++;
flood_fill(i, j);
}
}
}
cout << ans << "\n";
return 0;
}复杂度
- 时间:每个格子至多入队、出队一次,加一遍全网格扫描,
。 - 空间:网格数组与 BFS 队列,
。
总结
统计连通块是一类固定套路:发现一个未访问的目标点,答案加一,再搜索并标记整个连通块。本题只是把"相邻"定义为八方向,把网格本身当成图来遍历。rbook 的《图的遍历》与模板 connected-components 讲的就是同一套"枚举点 -> 发现新连通块 -> DFS/BFS 淹没"的结构。
图示解析
这张 ASCII 图展示整道题的解题路线:
text
题意:八方向相邻的 'W' 构成一个水塘,数水塘个数
|
| 关键观察:扫描到的第一个未访问 'W' 必然属于新水塘
v
Flood Fill(main.cpp)
扫描全网格
|-- 格子是 '.':跳过
`-- 格子是 'W':答案 +1,从它开始 BFS
BFS 规则:向 8 个方向(含对角)扩展
入队前把 'W' 改成 '.'(原地标记,避免重复处理)
|
v
答案 = BFS 启动的次数
每个格子最多进出队一次:时间 O(n*m),空间 O(n*m)图中上半部分解释"为什么扫到 W 就能确定是新水塘"——之前的 BFS 已经把所有旧水塘变成 .;下半部分是 BFS 的实现要点。整道题的困难只在两个细节:方向必须是八个(含对角线),以及入队前就标记防止重复。