ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

连通分量

2022-05-16 08:33:09  阅读:146  来源: 互联网

标签:连通 格子 int cnt ++ && 分量


连通分量

给定一个 $n \times m$ 的方格矩阵,每个方格要么是空格(用 . 表示),要么是障碍物(用 * 表示)。

如果两个空格存在公共边,则两空格视为相邻。

我们称一个不可扩展的空格集合为连通分量,如果集合中的任意两个空格都能通过相邻空格的路径连接。

这其实是一个典型的众所周知的关于连通分量(Connected Component )的定义。

现在,我们的问题如下:

对于每个包含障碍物的单元格 $\left( {x,y} \right)$,假设它是一个空格(所有其他单元格保持不变)的前提下,请你计算包含 $\left( {x,y} \right)$ 的连通分量所包含的单元格数量。

注意,所有假设求解操作之间都是相互独立的,互不影响。

输入格式

第一行包含两个整数 $n,m$。

接下来 $n$ 行,每行包含 $m$ 个字符: . 表示空格, * 表示障碍物。

输出格式

输出一个 $n$ 行 $m$ 列的字符矩阵,其中第 $i$ 行第 $j$ 列的字符对应给定矩阵中第 $i$ 行第 $j$ 列的单元格。

如果该单元格为空格,则输出字符为 . ,如果该单元格为障碍物,则输出字符为假设该单元格为空格的前提下,包含该单元格的连通分量所包含的单元格数量对 $10$ 取模后的结果。

具体格式可参照输出样例。

数据范围

前 $5$ 个测试点满足 $1 \leq n,m \leq 10$。
所有测试点满足 $1 \leq n,m \leq 1000$。

输入样例1:

3 3
*.*
.*.
*.*

输出样例1:

3.3
.5.
3.3

输入样例2:

4 5
**..*
..***
.*.*.
*.*.*

输出样例2:

46..3
..732
.6.4.
5.4.3

 

解题思路

  这题写的时候想到个最暴力的做法,就遍历每一个点,如果这个点是 * 就从这个点开始dfs,统计这个点所在连通块内的点的个数,时间复杂度为$O \left( {nm \times nm} \right)$,必然会超时。然后想了很久怎么去优化,才想到只需要一遍dfs,找到所有的连通块,再遍历每一个点,如果这个点是 * ,就看看这个位置的四个方向的格子,如果这个格子是 . 那么,表明这个点与这个格子的连通块是相连的,同时还要记得判重,这四个方向的格子可能存在多个格子属于同一个连通块的情况。即使想到想到这种做法,但代码熟练度不高,光调试都用了一个多小时,经常把变量名打错然后还发现不了。

  本质是求将某个点的上下左右四个方向的连通块合并完后,得到的新的连通块的大小。因此首先需要找到所有连通块,找连通块的方法有dfs,bfs(Flood Fill),并查集。

  对于有障碍物的格子,先看一下四个方向一共有几个连通块。如果四个方向都是空地,即 . ,假设这四个方向的格子用$a,b,c,d$来表示,然后看一下这四个格子分别隶属于哪个连通块,也就是每个连通块的代表元素,假设这四个方向的格子的所在连通块(并查集)的代表元素分别是${a'},{b'},{c'},{d'}$,再把属于同一个连通块的格子去掉(代表元素相同),判重后假设最终得到两个不同的连通块${a'},{b'}$,那么将这两个连通块(所有不同的连通块)与障碍物的格子连通起来,得到新的连通块的大小就是$cnt \left[ {a'} \right] + cnt \left[ {b'} \right] + 1$,$cnt$数组表示该连通块包含点的数目。因此我们还需要维护每个连通块的大小。

  同时,由于并查集一般都是用维护一维的矩阵,因此我们需要把二维矩阵展开,变成一维,即如果某个格子的坐标为$\left( {x, y} \right)$,矩阵展开后变成$x * col + y$,其中$col$为矩阵的列数。

  并查集,AC代码如下:

 1 #include <cstdio>
 2 #include <unordered_set>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 1010, M = N * N;
 7 
 8 char g[N][N];
 9 int fa[M], cnt[M];  // 通过矩阵展开把二维坐标变一维,因此可以用一维的并查集
10 int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
11 
12 int find(int x) {
13     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
14 }
15 
16 int main() {
17     int n, m;
18     scanf("%d %d", &n, &m);
19     for (int i = 0; i < n; i++) {
20         scanf("%s", g + i);
21     }
22     
23     for (int i = 0; i < n * m; i++) {
24         fa[i] = i;
25         cnt[i] = 1;
26     }
27     
28     // 求连通块
29     for (int i = 0; i < n; i++) {
30         for (int j = 0; j < m; j++) {
31             if (g[i][j] != '.') continue;
32             for (int k = 0; k < 4; k++) {
33                 int x = i + dx[k], y = j + dy[k];
34                 
35                 // 如果g[i][j] == '.'并且四个方向的格子也是'.',表明这两个格子在同一个连通块
36                 if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
37                     int p = find(x * m + y), q = find(i * m + j);
38                     if (p != q) {   // 两个格子还没有合并到同一个连通块
39                         cnt[q] += cnt[p];
40                         fa[p] = q;
41                     }
42                 }
43             }
44         }
45     }
46     
47     unordered_set<int> st;  // 需要把哈希表定义到循环外
48     for (int i = 0; i < n; i++) {
49         for (int j = 0; j < m; j++) {
50             if (g[i][j] == '.') {
51                 printf(".");
52             }
53             else {
54                 st.clear(); // 如果哈希表在这里定义会TLE
55                 for (int k = 0; k < 4; k++) {
56                     int x = i + dx[k], y = j + dy[k];
57                     if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
58                         st.insert(find(x * m + y)); // 判重
59                     }
60                 }
61                 
62                 int ret = 1;
63                 for (auto &it : st) {
64                     ret += cnt[it];
65                 }
66                 printf("%d", ret % 10);
67             }
68         }
69         printf("\n");
70     }
71     
72     return 0;
73 }

   Flood Fill,dfs写法,AC代码如下:

 1 #include <cstdio>
 2 #include <unordered_set>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 1010, M = N * N;
 7 
 8 int n, m;
 9 char g[N][N];
10 int mp[M], cnt[M], sz;  // mp[i]表示第i个格子隶属于哪个连通块,cnt[i]表示第i个连通块的大小
11 bool vis[N][N];
12 int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
13 
14 int dfs(int x, int y) { // dfs返回当前连通块包含的点数
15     vis[x][y] = true;
16     mp[x * m + y] = sz;
17     
18     int cnt = 1;
19     for (int i = 0; i < 4; i++) {
20         int xx = x + dx[i], yy = y + dy[i];
21         if (xx >= 0 && xx < n && yy >= 0 && yy < m && g[xx][yy] == '.' && !vis[xx][yy]) {
22             cnt += dfs(xx, yy);
23         }
24     }
25     
26     return cnt;
27 }
28 
29 int main() {
30     scanf("%d %d", &n, &m);
31     for (int i = 0; i < n; i++) {
32         scanf("%s", g + i);
33     }
34     
35     for (int i = 0; i < n; i++) {
36         for (int j = 0; j < m; j++) {
37             if (g[i][j] == '.' && !vis[i][j]) {
38                 cnt[sz++] = dfs(i, j);  // 第sz个连通块的数量为cnt[sz],同时搜完后sz+1
39             }
40         }
41     }
42     
43     unordered_set<int> st;
44     for (int i = 0; i < n; i++) {
45         for (int j = 0; j < m; j++) {
46             if (g[i][j] == '.') {
47                 printf(".");
48             }
49             else {
50                 st.clear();
51                 for (int k = 0; k < 4; k++) {
52                     int x = i + dx[k], y = j + dy[k];
53                     if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == '.') {
54                         st.insert(mp[x * m + y]);
55                     }
56                 }
57                 
58                 int ret = 1;
59                 for (auto &it : st) {
60                     ret += cnt[it];
61                 }
62                 printf("%d", ret % 10);
63             }
64         }
65         printf("\n");
66     }
67     
68     return 0;
69 }

 

参考资料

  AcWing 4420. 连通分量(AcWing杯 - 周赛):https://www.acwing.com/video/3870/

标签:连通,格子,int,cnt,++,&&,分量
来源: https://www.cnblogs.com/onlyblues/p/16275260.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有