4420 连通分量(并查集--统计每个集合中点的数目)

1. 问题描述:

给定一个 n × m 的方格矩阵,每个方格要么是空格(用 . 表示),要么是障碍物(用 * 表示),如果两个空格存在公共边,则两空格视为相邻。我们称一个不可扩展的空格集合为连通分量,如果集合中的任意两个空格都能通过相邻空格的路径连接。这其实是一个典型的众所周知的关于连通分量的定义。现在,我们的问题如下:
对于每个包含障碍物的单元格 (x,y),假设它是一个空格(所有其他单元格保持不变)的前提下,请你计算包含 (x,y) 的连通分量所包含的单元格数量,注意,所有假设求解操作之间都是相互独立的,互不影响。

输入格式

第一行包含两个整数 n,m,接下来 n 行,每行包含 m 个字符:. 表示空格,* 表示障碍物

输出格式

输出一个 n 行 m 列的字符矩阵,其中第 i 行第 j 列的字符对应给定矩阵中第 i 行第 j 列的单元格。如果该单元格为空格,则输出字符为 .,如果该单元格为障碍物,则输出字符为假设该单元格为空格的前提下,包含该单元格的连通分量所包含的单元格数量对 10 取模后的结果。具体格式可参照输出样例。

数据范围

前 5 个测试点满足 1 ≤ n,m ≤ 10
所有测试点满足 1 ≤ n,m ≤ 1000

输入样例1:

3 3
*.*
.*.
*.*

输出样例1:

3.3
.5.
3.3

输入样例2:

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

输出样例2:

46..3
..732
.6.4.
5.4.3
来源:https://www.acwing.com/problem/content/description/4423/

2. 思路分析:

分析题目可以知道我们只需要考虑障碍物周围为 "." 的数目即可,所以我们可以将为 "." 的连通区域合并为一个集合,合并集合本质上是求解连通块,可以使用 dfs,bfs 或者并查集来解决,对于这道题目来说使用并查集来合并同一个集合中的元素那么后面可以判断周围的元素是否属于同一个集合比较方便,所以下面使用并查集来解决。我们可以枚举矩阵中的每一个元素,对于当前为 "." 的位置 (x,y),遍历 (x,y) 的上下左右四个方向,如果周围的方向存在 "." 那么将其合并到同一个集合中,由于并查集的的查找和合并操作都是在一维数组上完成的,所以我们需要将矩阵中的二维坐标映射到一维坐标,当我们遍历完整个矩阵之后那么所有 "." 连通的区域都会合并到同一个集合中,由于后面需要计算集合中元素的数目所以我们还需要声明一个一维数组 s 来记录集合中元素的数目(记录集合中元素的数目是并查集的基本操作,a 合并到 b 那么 s[b] 需要累加上 s[a]),最后我们可以遍历矩阵中的所有元素,如果当前枚举的元素为 "." 那么直接输出即可,如果为 "*" 那么需要求解上下左右四个方向所在集合的元素数目,由于上下左右四个方向的 "." 的位置可能属于同一个集合,这里使用并查集的一个好处是能够使用 find 函数查找出四个方向所在集合的父节点,父节点相同说明在同一个集合,为了防止重复计算点的数目我们可以查找出上下左右四个方向的父节点加入到 set 集合或者 map 中去重,这样 set 或者 map 保存的就是不同集合中的元素,最后累加上下左右四个方向不属于同一个集合中元素的点的数目 + 1 然后对 10 取余就是当前位置为 "*" 的答案。:

3. 代码如下:

python(超时):

from typing import List


class Solution:
    # 查找x的父节点并且在查找节点的过程中进行路径合并
    def find(self, x: int, p: List[int]):
        if x != p[x]:
            p[x] = self.find(p[x], p)
        return p[x]

    # 将二维坐标映射到一维坐标方面后面的并查集中一维列表的操作
    def get(self, x: int, y: int, m: int):
        return x * m + y

    def process(self):
        n, m = map(int, input().split())
        g = list()
        for i in range(n):
            g.append(list(input()))
        # s 记录并查集集合中点的数目
        s, p = [1] * (n * m), [i for i in range(n * m)]
        pos = [[0, 1], [0, -1], [1, 0], [-1, 0]]
        for i in range(n):
            for j in range(m):
                # 只有当是.的时候才进行集合
                if g[i][j] == ".":
                    for k in range(4):
                        x, y = i + pos[k][0], j + pos[k][1]
                        if 0 <= x < n and 0 <= y < m and g[x][y] == ".":
                            a, b = self.find(self.get(i, j, m), p), self.find(self.get(x, y, m), p)
                            if a != b:
                                # 将节点a合并到节点b上
                                p[a] = b
                                s[b] += s[a]
        for i in range(n):
            for j in range(m):
                if g[i][j] == ".":
                    print(".", end="")
                else:
                    tot = 1
                    # 求解上下左右四个方向为.的位置的代表元素
                    fathers = list()
                    for k in range(4):
                        a, b = i + pos[k][0], j + pos[k][1]
                        if 0 <= a < n and 0 <= b < m and g[a][b] == ".":
                            fathers.append(self.find(self.get(a, b, m), p))
                    t = set(fathers)
                    for x in t:
                        tot += s[x]
                    print(tot % 10, end="")
            print()


if __name__ == '__main__':
    Solution().process()

go:

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

// 并查集查找x的父节点的操作
func find(x int, p []int) int {
	if x != p[x] {
		p[x] = find(p[x], p)
	}
	return p[x]
}

// 将二维坐标映射到一维坐标
func get(x int, y int, m int) int {
	return x*m + y
}

func run(r io.Reader, w io.Writer) {
	// 使用bufio包中的相关方法优化读取和输出数据的效率
	in := bufio.NewReader(r)
	out := bufio.NewWriter(w)
	// 将缓存数据写入到标准输出中
	defer out.Flush()
	var (
		n, m int
		str  string
		g    []string
	)
	fmt.Fscan(in, &n, &m)
	for i := 0; i < n; i++ {
		fmt.Fscan(in, &str)
		g = append(g, str)
	}
	p, s := make([]int, n*m), make([]int, n*m)
	// 初始化并查集的父节点切片p和统计并查集中每个集合中元素的个数s
	for i := 0; i < n*m; i++ {
		p[i] = i
		// 一开始的时候每一个节点都有一个点
		s[i] = 1
	}
	// 右左下上四个方向
	pos := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
	var a, b int
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if g[i][j] == '.' {
				// 合并周围四个方向的集合元素
				for k := 0; k < 4; k++ {
					a, b = i+pos[k][0], j+pos[k][1]
					if a >= 0 && a < n && b >= 0 && b < m && g[a][b] == '.' {
						a, b = find(get(i, j, m), p), find(get(a, b, m), p)
						// 将a合并到集合b中
						if a != b {
							p[a] = b
							s[b] += s[a]
						}
					}
				}
			}
		}
	}
	// 枚举每一个元素输出答案
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if g[i][j] == '.' {
				fmt.Fprint(out, ".")
			} else {
				// tot记录上下左右为点的代表元素所在集合的点的数目
				tot := 1
				// 使用哈希表来去重, 键为父节点, 值为父节点所在集合的点的数目
				mp := make(map[int]int)
				for k := 0; k < 4; k++ {
					a, b = i+pos[k][0], j+pos[k][1]
					if a >= 0 && a < n && b >= 0 && b < m && g[a][b] == '.' {
						x := find(get(a, b, m), p)
                        // 哈希表的值存储集合中点的数目
						mp[x] = s[x]
					}
				}
                // 统计周围的点的数目
				for _, v := range mp {
					tot += v
				}
				fmt.Fprint(out, tot%10)
			}
		}
		fmt.Fprintln(out)
	}
}

func main() {
	run(os.Stdin, os.Stdout)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值