网格四连通 flood fill 计数黑格连通块:每遇未访问黑格开块、栈式淹没;数据行被拼成整型 token,读入需复刻评测程序的 int32 溢出语义。
OJ: roj
题目 ID: 1335
难度:入门
标签:网格连通块flood-fill搜索python
创建: 2026-09-30 05:43
更新: 2026-10-05 10:10
后置题目
roj 3081乳草的入侵B 的八连通 BFS 直接用 A 教的「扩展条件里一次挡掉越界与障碍、入队即标记」这一网格搜索骨架(B 代码同样先判 in_map 再把 dist 置为 week+1 后 append),A 还点明八连通只需扩 DIRS;B 只把栈换成队列并叠加层数记录与取全图最大值,得到最晚占领周数。roj 1250The CastleB 的 main.cpp 外层双循环与 A 教的「扫到未访问格就开新分量并 flood fill 淹没」完全同构,只把 A 的四连通黑格邻接换成位掩码判墙,再叠加统计分量大小与取最大面积。roj 1248Dungeon MasterB 的三维网格 BFS 在六方向扩展时沿用 A 明确点出的「入栈/入队即标记」这一状态设计(先判界再判障碍、命中即就地标记 seen),只是把连接块计数换成 BFS 层号即最短分钟数并叠加三维偏移与多组数据解析,属模板级复用。roj 1191流感传染B 的按天分层多源 BFS 在四邻扩展时直接沿用 A 教的越界+目标格判定后在入队时立即标记的状态设计,只是把单一淹水源换成全体患者作初始层、把层数当天数;A 的连通块计数主难点(外层开新块)B 未复用,故为模板级。luogu P1162填涂颜色B 的解法二把 A 教的「扫描未访问格后栈式淹没整块、入栈即标记」连通块 flood fill 原样用在 BFS2 上,只把计数换成改写成 2,并在其上叠加正难则反判内外这一 A 未教的新步骤。luogu P1332血色先锋队B 的多源 BFS 直接沿用 A 教的网格四邻扩展模板(越界判定 + 未访问才入队并即时标记),只把单个入栈起点换成全部感染源
题目描述
给定 1)或白(0)。若两个黑格上下左右相邻则属于同一个四连通黑格连通块。求连通块个数。
输入:第一行 n m,接下来 0/1。输出:一行 ans,连通块个数。
样例 111 / 010 / 101 共
思路
外层扫全图,碰到未访问的黑格就开一个新块(块数 +1)并从它出发做栈式 flood fill 把整块淹没;入栈时立即标记,每个格子至多入栈一次,总访问量
参考代码
Python 版:
python
#!/usr/bin/env python3
# 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-09-30 05:44
# update_at: 2026-09-30 06:10
# 数据说明:测试数据把每行 0/1 拼成一个长整型 token,评测程序 std.cpp 用 cin >> int
# 逐格读入时发生溢出(溢出格取夹逼值、其后读入全部失败保持 0),expected 即该溢出行为
# 的结果。read_grid 复刻这条读入语义以对齐真实数据;count_blocks 才是题面的连通块计数。
import sys
from collections.abc import Iterator
DIRS = ((-1, 0), (1, 0), (0, -1), (0, 1)) # 四连通:上、下、左、右
INT_MAX = 2**31 - 1 # C++ int 上界:溢出格写入夹逼值(与 std.cpp 的读入语义一致)
INT_MIN = -(2**31)
def read_grid(tokens: Iterator[int], n: int, m: int) -> list[list[int]]:
"""按标准程序 std.cpp 的读入语义切格子:逐个检查 int32 边界,溢出即置失败,其后格子保持 0。"""
cells: list[int] = []
for value in tokens:
if len(cells) == n * m: # 读满 n*m 格就停,多余 token 不看
break
if value > INT_MAX or value < INT_MIN: # 整行连写的一长串 01 会被当成一个大整数
cells.append(INT_MAX) # 溢出:当前格取非零夹逼值,之后的读入全部失败
break
cells.append(value)
cells += [0] * (n * m - len(cells)) # 读失败 / EOF:剩余格子保持 0(白格)
return [cells[i * m:(i + 1) * m] for i in range(n)]
def count_blocks(grid: list[list[int]]) -> int:
"""flood fill 计数:每碰到非 0 且未访问的格子开一个新连通块,再用栈淹没整块。"""
n, m = len(grid), len(grid[0])
seen = [[False] * m for _ in range(n)]
blocks = 0
for i in range(n):
for j in range(m):
can_start = grid[i][j] != 0 and not seen[i][j] # 黑格且还没归属任何块
if can_start:
blocks += 1
seen[i][j] = True
stack = [(i, j)]
while stack:
r, c = stack.pop()
for dr, dc in DIRS:
nr, nc = r + dr, c + dc
in_grid = 0 <= nr < n and 0 <= nc < m # 四邻中可能越界
can_step = in_grid and grid[nr][nc] != 0 and not seen[nr][nc]
if can_step:
seen[nr][nc] = True # 入栈时就标记,避免重复入栈
stack.append((nr, nc))
return blocks
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n, m = next(data), next(data)
grid = read_grid(data, n, m)
print(count_blocks(grid))
if __name__ == "__main__":
solve()C++ 版(同一算法):
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-10-05 10:10
* update_at: 2026-10-05 10:10
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 105;
int n, m;
int a[MAXN][MAXN]; // a[i][j]:网格格子,0 白 1 黑
int seen[MAXN][MAXN]; // seen[i][j]:格子 (i,j) 是否已归属某块,0/1 标记
int dr[4] = {-1, 1, 0, 0}; // 四连通方向:上、下、左、右
int dc[4] = {0, 0, -1, 1};
// 从 (sr, sc) 出发用栈淹没整块黑格,每入栈一格立即标记,避免重复入栈。
void flood(int sr, int sc) {
seen[sr][sc] = 1;
stack<pair<int,int>> stk;
stk.push({sr, sc});
while (!stk.empty()) {
pair<int,int> cur = stk.top();
stk.pop();
int r = cur.first;
int c = cur.second;
for (int k = 0; k < 4; k++) {
int nr = r + dr[k];
int nc = c + dc[k];
// 越界 / 白格 / 已归属 一并挡掉
if (nr < 0 || nr >= n || nc < 0 || nc >= m) continue;
if (a[nr][nc] == 0) continue;
if (seen[nr][nc]) continue;
seen[nr][nc] = 1;
stk.push({nr, nc});
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
}
}
// 外层扫全图:每碰到未访问的黑格就开一个新块并淹没。
int blocks = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] != 0 && !seen[i][j]) {
blocks++;
flood(i, j);
}
}
}
cout << blocks << "\n";
return 0;
}