其他分享
首页 > 其他分享> > 连通分量

连通分量

作者:互联网

连通分量

给定一个 $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