当前位置: 首页 > news >正文

bfs搜索加标记连通区域id实现时间优化(空间换时间)

有一个仅由数字 0 与 1 组成的 n×n 格迷宫。若你位于一格 0 上,那么你可以移动到相邻 4 格中的某一格 1 上,同样若你位于一格 1 上,那么你可以移动到相邻 4 格中的某一格 0 上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式

第一行为两个正整数 n,m。

下面 n 行,每行 n 个字符,字符只可能是 0 或者 1,字符之间没有空格。

接下来 m 行,每行两个用空格分隔的正整数 i,j,对应了迷宫中第 i 行第 j 列的一个格子,询问从这一格开始能移动到多少格。

输出格式

m 行,对于每个询问输出相应答案。

输入输出样例

输入 #1复制

2 2
01
10
1 1
2 2

输出 #1复制

4
4

说明/提示

对于样例,所有格子互相可达。

  • 对于 20% 的数据,n≤10;
  • 对于 40% 的数据,n≤50;
  • 对于 50% 的数据,m≤5;
  • 对于 60% 的数据,n,m≤100;
  • 对于 100% 的数据,1≤n≤1000,1≤m≤100000。

#include <iostream>

#include <vector>

#include <queue>

using namespace std;

// 定义移动方向:上、下、左、右

const int directions[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

// BFS函数:从(i,j)开始探索连通区域,并返回区域大小

int bfs(int i, int j, vector<string>& grid, vector<vector<int>>& component, int componentId) {

    queue<pair<int, int>> q;

    q.push({i, j});

    component[i][j] = componentId;

    int size = 0;

    while (!q.empty()) {

        auto current = q.front();

        q.pop();

        int x = current.first;

        int y = current.second;

        size++;

        for (int d = 0; d < 4; ++d) {

            int nx = x + directions[d][0];

            int ny = y + directions[d][1];

            if (nx >= 0 && nx < grid.size() && ny >= 0 && ny < grid.size()) {

                if (grid[x][y] != grid[nx][ny] && component[nx][ny] == 0) {

                    component[nx][ny] = componentId;

                    q.push({nx, ny});

                }

            }

        }

    }

    return size;

}

int main() {

    ios::sync_with_stdio(false);

    cin.tie(nullptr);

    int n, m;

    cin >> n >> m;

    vector<string> grid(n);

    for (int i = 0; i < n; ++i) {

        cin >> grid[i];

    }

    vector<vector<int>> component(n, vector<int>(n, 0));

    vector<int> componentSize;

    int componentId = 1;

    for (int i = 0; i < n; ++i) {

        for (int j = 0; j < n; ++j) {

            if (component[i][j] == 0) {

                int size = bfs(i, j, grid, component, componentId);

                componentSize.push_back(size);

                componentId++;

            }

        }

    }

    while (m--) {

        int i, j;

        cin >> i >> j;

        i--; j--;

        cout << componentSize[component[i][j] - 1] << '\n';

    }

    return 0;

}

我用最直观的方式解释为什么 连通区域ID + 预处理 能大幅提升查询效率,而 每次查询单独BFS 会很慢。就像查字典和现场数数的区别!


场景类比

假设你管理一个学校,学生分成若干朋友圈(连通区域)。现在有 m 个问题,每个问题问:“某学生的朋友圈有多少人?”

方法1:每次现场数(单独BFS)
  • 操作:每次有人提问,你就去教室找到这个学生,现场数他朋友圈的人数。

  • 问题:如果同一个朋友圈被问多次(比如100次),你会重复数100次,效率极低。

方法2:提前统计 + 查表(连通区域ID)
  • 操作

    1. 预处理:提前把所有朋友圈找出来,给每个朋友圈编号(ID),并统计好人数。

    2. 查表:遇到提问时,直接根据编号查表回答。

  • 优势:无论问多少次,每个朋友圈只需统计一次,后续全是 O(1) 查询。


在迷宫问题中的具体体现

1. 单独BFS的问题
  • 每次查询:都要从起点重新BFS,可能重复遍历相同区域。
    例子
    如果查询 (1,1) 和 (2,2) 属于同一区域,你会两次BFS遍历整个区域,浪费时间。

2. 预处理 + 连通区域ID的优势
  • 预处理阶段
    用一次全图遍历(O(n²)),标记所有格子的区域ID,并记录每个区域的大小。

    • component[i][j]:格子 (i,j) 的区域ID。

    • componentSize[id]:区域 id 的大小。

  • 查询阶段
    直接通过 component[i][j] 找到区域ID,再用 componentSize 查大小,只需 O(1) 时间。

关键点

  • 空间换时间:预处理阶段多用了 O(n²) 的空间存储 component 数组,但将查询时间从 O(n²) 降到 O(1)

  • 避免重复计算:同一区域的查询只需计算一次。


复杂度对比

方法预处理时间单次查询时间m次查询总时间
每次单独BFSO(n²)O(m·n²)
预处理 + 连通区域IDO(n²)O(1)O(n² + m)

当 m 很大时(比如 m=1e5):

  • 单独BFS:1e5 × 1e6 = 1e11 次操作(不可接受)。

  • 预处理:1e6 + 1e5 ≈ 1e6 次操作(快1e5倍)。


例子说明

迷宫示例

复制

下载

0 1 0
1 0 1
0 1 0
  • 连通区域:所有格子互通,区域ID=1,大小=5。

  • 查询 (1,1) 和 (2,3)

    • 单独BFS:两次完整BFS,遍历5个格子各两次 → 共10次操作。

    • 预处理:预处理遍历5格子,查询直接读表 → 共5+2=7次操作。

更极端例子
  • 迷宫全为 0(只有一个区域,大小n²)。

  • 查询m次同一区域:

    • 单独BFS:m × n² 次操作。

    • 预处理:n² + m 次操作。


为什么 component[i][j] - 1

  • componentSize 是数组,下标从 0 开始。

  • 区域ID从 1 开始(因为 0 表示未访问)。

  • 所以区域 id 的大小存储在 componentSize[id-1]


该用哪种方法?

  • 预处理 + ID:适合 查询次数多(m很大) 或 同一区域被多次查询

  • 单独BFS:适合 查询次数极少(m很小) 或 迷宫极小


最终结论

连通区域ID的本质是 提前划分好所有“团伙”并统计信息,后续查询直接“查表”,避免重复劳动。就像提前统计好班级人数,以后随问随答,不用每次都去教室数人头!

相关文章:

  • Go语言八股之Mysql事务
  • 扬州卓韵酒店用品:优质洗浴用品,提升酒店满意度与品牌形象
  • TCP(传输控制协议)建立连接的过程
  • Git/GitLab日常使用的命令指南来了!
  • 前端代码生成博客封面图片
  • 寻找两个正序数组的中位数 - 困难
  • 【BotSharp详细介绍——一步步实现MCP+LLM的聊天问答实例】
  • vscode c++编译onnxruntime cuda 出现的问题
  • 浏览器宝塔访问不了给的面板地址
  • 运维职业发展思维导图
  • 幼儿学前教育答辩词答辩技巧问题答辩自述稿
  • React Native/Flutter 原生模块开发
  • BGP实验(联邦及反射器)
  • SQL:MySQL函数:条件函数(Conditional Functions)
  • Day 21 训练
  • Spring+LangChain4j小智医疗项目
  • 如何让open-mpi在不同版本的OS上运行
  • java方法的练习题
  • Python内存管理:赋值、浅拷贝与深拷贝解析
  • 数智管理学(九)
  • 王东杰评《国家与学术》︱不“国”不“故”的“国学”
  • 专家:家长要以身作则,孩子是模仿者学习者有时也是评判者
  • 丹麦外交大臣拉斯穆森将访华
  • “饿了么”枣庄一站点两名连襟骑手先后猝死,软件显示生前3天每日工作超11小时
  • 全球前瞻|特朗普访问中东三国,印巴军方将于12日再次对话
  • 全球医药股普跌,A股创新药板块下挫