题目描述

岛屿地图有 nn 行(n⩽100n \leqslant 100),每行不超过 200 个字符,各行长度可以不同。格子是空格(大海)、*(河流或山丘)或小写字母(一户人家),上下左右共边的两户算相邻,互相可达的人家属于同一个家族,求家族个数。输入第一行是 nn,接下来 nn 行是地图(行首可能有空格)。

plaintext
输入:          输出:
4               3
*zlw**pxh
l*zlwk*hx*
w*tyy**yyy 
    zzl

思路

家族就是字母格子的四连通块,* 和空格都是断开的障碍。从左到右、从上到下扫描,遇到未访问的字母格就把答案 +1,并用显式栈的洪水填充把整个连通块染色(长链可能让递归爆栈)。注意读入必须保留行首空格,越界按每行实际长度判断。

参考代码

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-10-02 17:15
# update_at: 2026-10-02 17:15

import sys


def flood_fill(start: tuple[int, int], grid: list[str], seen: set[tuple[int, int]]) -> None:
    """从 start 出发,把上下左右连通的同一片字母区域全部标记为已访问。"""
    stack = [start]
    while stack:
        x, y = stack.pop()
        for adj in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
            nx, ny = adj
            in_map = 0 <= nx < len(grid) and 0 <= ny < len(grid[nx])
            if in_map and grid[nx][ny].isalpha() and adj not in seen:
                seen.add(adj)  # 入栈前先标记,避免同一个格子被重复入栈
                stack.append(adj)


def solve() -> None:
    lines = sys.stdin.read().splitlines()
    n = int(lines[0])
    grid = lines[1:n + 1]  # 每行原样保留前导空格:空格是大海,'*' 是阻隔,字母是人家

    seen: set[tuple[int, int]] = set()
    families = 0
    for r, row in enumerate(grid):
        for c, ch in enumerate(row):
            if ch.isalpha() and (r, c) not in seen:
                families += 1  # 发现一个没访问过的字母点,就多了一个新家族
                flood_fill((r, c), grid, seen)

    print(families)


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-06 17:02
 * update_at: 2026-10-06 17:02
 */

#include <cstdio>
#include <string>
#include <stack>
using namespace std;

typedef long long ll;

const int MAXN = 105;   // 行数上限
const int MAXM = 205;   // 每行字符数上限

int n;                          // 地图行数
int len[MAXN];                  // len[i] 表示第 i 行实际存了几个字符
char grid[MAXN][MAXM];          // grid[i][j] 存地图字符,下标从 0 开始
bool vis[MAXN][MAXM];           // vis[i][j] 表示该字母格是否已被染过色

const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};

// 洪水填充:从 (r, c) 出发把整个家族的字母格都标记成已访问
// 用显式栈写成迭代 DFS,避免长链递归爆栈
void flood(int r, int c) {
    stack< pair<int, int> > st;
    st.push(make_pair(r, c));
    vis[r][c] = true;   // 入栈前先标记,防止重复入栈

    while (!st.empty()) {
        int x = st.top().first;
        int y = st.top().second;
        st.pop();

        // 枚举上下左右四个方向的字母格
        for (int d = 0; d < 4; ++d) {
            int nx = x + dx[d];
            int ny = y + dy[d];
            if (nx < 0 || nx >= n || ny < 0 || ny >= len[nx]) continue; // 越界(各行长度不同)
            if (vis[nx][ny]) continue;
            if (grid[nx][ny] < 'a' || grid[nx][ny] > 'z') continue;     // 只走字母格
            vis[nx][ny] = true;
            st.push(make_pair(nx, ny));
        }
    }
}

int main() {
    scanf("%d", &n);
    // getchar 吃掉数字后面的换行
    getchar();
    for (int i = 0; i < n; ++i) {
        // 用 fgets 逐行读入,保留行首空格(大海)
        fgets(grid[i], MAXM, stdin);
        len[i] = 0;
        // fgets 可能把末尾换行读进来,统一截掉
        while (grid[i][len[i]] != '\0' && grid[i][len[i]] != '\n' && grid[i][len[i]] != '\r') {
            ++len[i];
        }
        grid[i][len[i]] = '\0';
    }

    int ans = 0; // 家族数:每发现一个未访问的字母格就 +1
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < len[i]; ++j) {
            if (!vis[i][j] && grid[i][j] >= 'a' && grid[i][j] <= 'z') {
                ++ans;
                flood(i, j);
            }
        }
    }

    printf("%d\n", ans);
    return 0;
}