这里写自定义目录标题
更多精彩内容
这里是带你游历编程世界的Dashcoding编程社,我是Dash/北航硕士/ICPC区域赛全国排名30+/给你呈现我们眼中的世界!
256题算法特训课,帮你斩获大厂60W年薪offer
原题
米哈游校招真题RGB矩阵
B站动画详解
问题分析
这道题目要求我们计算在一个由红色(R)、绿色(G)和蓝色(B)三种颜色组成的矩阵中,由于小明的视觉缺陷导致他无法区分蓝色和绿色,进而影响他对矩阵连通块数量的判断。具体来说,连通块是指颜色相同且上下左右相邻的格子集合。我们需要计算小明视角下的连通块数量与实际连通块数量的差值。
解决这个问题的思路可以分为两步:
- 计算实际的连通块数量:首先,在不考虑小明的视觉缺陷情况下,使用深度优先搜索(DFS)算法遍历整个矩阵,统计实际的连通块数量。
- 模拟小明的视角:由于小明无法区分绿色和蓝色,我们将矩阵中所有的绿色(G)转换为蓝色(B),然后再次使用DFS统计在这种情况下的连通块数量。通过比较实际的连通块数量和小明视角下的连通块数量,我们可以得到小明视角比实际少的连通块数量。
深度优先搜索(DFS)适合这种图遍历问题,它通过递归的方式遍历所有相邻的相同颜色的格子,直到整个连通块被完全标记为已访问。该算法能够保证所有连通块被正确识别并统计。
思路分析
在这个问题中,我们需要进行两次深度优先搜索(DFS)来解决问题。首先,遍历整个矩阵,对每一个尚未访问的格子进行DFS,将该格子所在的整个连通块标记为已访问,并统计连通块的数量。这一步统计的是实际情况下的连通块数量。
然后,我们将所有的绿色(G)格子转换为蓝色(B),再进行一次DFS遍历,计算小明视角下的连通块数量。因为在小明的视角中,绿色和蓝色无法区分,所以在第二次遍历时,这两种颜色会被视为同一种颜色,导致连通块数量减少。
具体的DFS过程是从一个未访问的格子出发,递归地访问与该格子上下左右相邻且颜色相同的所有格子,直到整个连通块被标记为已访问。每次找到一个新的未访问格子时,就意味着发现了一个新的连通块。
最后,将实际的连通块数量减去小明视角下的连通块数量,即为答案。
算法实现
dx
和dy
数组表示上下左右四个方向的位移,用于在 DFS 中扩展到相邻格子。dfs
函数是深度优先搜索的核心,通过递归访问所有相邻且未访问的相同颜色的格子。query
函数遍历整个矩阵,统计未访问的连通块数量,每次发现一个新的连通块时调用dfs
进行标记。- 主函数中首先进行第一次
query
来统计实际的连通块数量。然后将所有的绿色格子转换为蓝色,再进行一次query
来统计小明视角下的连通块数量,最后输出两者的差值。
代码详解
标准代码程序
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
char a[N][N];
bool used[N][N];
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
// 深度优先搜索函数
void dfs(int x, int y, char c) {
used[x][y] = true;
for (int k = 0; k < 4; k++) {
int i = x + dx[k], j = y + dy[k];
if (i < 0 || i >= n || j < 0 || j >= m || used[i][j] || a[i][j] != c)
continue;
dfs(i, j, c);
}
}
// 计算连通块数量
int query() {
int num = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (!used[i][j]) {
dfs(i, j, a[i][j]);
num++;
}
}
}
return num;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++)
cin >> a[i];
int num1 = query(); // 实际连通块数量
// 重置used数组
memset(used, 0, sizeof(used));
// 将所有绿色格子转换为蓝色
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == 'G')
a[i][j] = 'B';
}
}
int num2 = query(); // 小明视角下的连通块数量
// 输出实际连通块数量与小明视角下连通块数量的差值
cout << num1 - num2;
return 0;
}
Java代码
import java.util.Scanner;
public class ColorBlindness {
static int n, m;
static char[][] a;
static boolean[][] used;
static int[] dx = {1, -1, 0, 0};
static int[] dy = {0, 0, 1, -1};
public static void dfs(int x, int y, char c) {
used[x][y] = true;
for (int k = 0; k < 4; k++) {
int i = x + dx[k], j = y + dy[k];
if (i < 0 || i >= n || j < 0 || j >= m || used[i][j] || a[i][j] != c)
continue;
dfs(i, j, c);
}
}
public static int query() {
int num = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (!used[i][j]) {
dfs(i, j, a[i][j]);
num++;
}
}
}
return num;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
a = new char[n][m];
used = new boolean[n][m];
for (int i = 0; i < n; i++) {
a[i] = scanner.next().toCharArray();
}
int num1 = query();
// Reset used array
used = new boolean[n][m];
// Convert all 'G' to 'B'
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == 'G')
a[i][j] = 'B';
}
}
int num2 = query();
System.out.println(num1 - num2);
}
}
Python代码
def dfs(x, y, c):
used[x][y] = True
for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
i, j = x + dx, y + dy
if 0 <= i < n and 0 <= j < m and not used[i][j] and a[i][j] == c:
dfs(i, j, c)
def query():
num = 0
for i in range(n):
for j in range(m):
if not used[i][j]:
dfs(i, j, a[i][j])
num += 1
return num
n, m = map(int, input().split())
a = [list(input()) for _ in range(n)]
used = [[False] * m for _ in range(n)]
num1 = query()
# Reset used array
used = [[False] * m for _ in range(n)]
# Convert all 'G' to 'B'
for i in range(n):
for j in range(m):
if a[i][j] == 'G':
a[i][j] = 'B'
num2 = query()
print(num1 - num2)
Javascript代码
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let n, m;
let a = [];
let used = [];
const dx = [1, -1, 0, 0];
const dy = [0, 0, 1, -1];
function dfs(x, y, c) {
used[x][y] = true;
for (let k = 0; k < 4; k++) {
let i = x + dx[k], j = y + dy[k];
if (i >= 0 && i < n && j >= 0 && j < m && !used[i][j] && a[i][j] === c) {
dfs(i, j, c);
}
}
}
function query() {
let num = 0;
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (!used[i][j]) {
dfs(i, j, a[i][j]);
num++;
}
}
}
return num;
}
rl.on('line', (line) => {
if (!n) {
[n, m] = line.split(' ').map(Number);
} else {
a.push(line.split(''));
if (a.length === n) {
used = Array.from({ length: n }, () => Array(m).fill(false));
const num1 = query();
// Reset used array
used = Array.from({ length: n }, () => Array(m).fill(false));
// Convert all 'G' to 'B'
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (a[i][j] === 'G') a[i][j] = 'B';
}
}
const num2 = query();
console.log(num1 - num2);
rl.close();
}
}
});
复杂度分析
时间复杂度
在这道题中,主要的操作集中在深度优先搜索(DFS)和遍历矩阵上。我们可以通过分析以下步骤来得出时间复杂度:
-
DFS 操作: DFS 的复杂度为 O ( N × M ) O(N \times M) O(N×M),其中 N N N 和 M M M 分别表示矩阵的行数和列数。每次 DFS 将访问一个新的连通块中的所有格子,因此最多访问整个矩阵中的所有格子一次。
-
两次遍历操作: 题目要求我们进行两次连通块统计。第一次统计时按照实际颜色分布,第二次统计时将所有的绿色(G)转换为蓝色(B)后再进行统计。这两次遍历都涉及到 O ( N × M ) O(N \times M) O(N×M) 的复杂度。
综上所述,总的时间复杂度为 O ( N × M ) O(N \times M) O(N×M)。由于题目中的 N N N 和 M M M 的最大值为 1000,因此算法可以有效处理最大规模的输入。
空间复杂度
空间复杂度主要由以下几个部分组成:
-
存储矩阵的空间: 我们使用一个大小为 N × M N \times M N×M 的二维数组来存储矩阵,因此空间复杂度为 O ( N × M ) O(N \times M) O(N×M)。
-
标记访问情况的空间: 我们需要一个与矩阵相同大小的二维数组 used 来记录每个格子是否已经访问过,因此空间复杂度同样为 O ( N × M ) O(N \times M) O(N×M)。
-
递归栈的空间: DFS 的递归深度与矩阵的大小有关,最坏情况下,递归深度为 O ( N × M ) O(N \times M) O(N×M),但通常情况下,递归深度要远小于矩阵大小。
因此,总的空间复杂度为 O ( N × M ) O(N \times M) O(N×M)。
总结
这道题目通过模拟小明在面对颜色区分困难时的视角变化,考察了我们对于图遍历和连通块统计的理解。题目中的核心是通过深度优先搜索(DFS)算法遍历整个矩阵,以统计连通块的数量。为了模拟小明的视觉缺陷,我们将绿色(G)转换为蓝色(B),从而使得小明视角下的连通块数量减少。最后通过两次连通块统计,我们计算出实际连通块数量与小明视角下连通块数量的差值,进而得到答案。
这道题目较好地结合了图论中的基础概念(如连通块)和搜索算法(DFS),其解决思路简洁明了,适合在复杂度较大的矩阵中应用。在处理矩阵类问题时,DFS 是一种有效且常用的遍历策略。通过合理的递归和边界处理,DFS 可以帮助我们快速解决这类涉及连通性的题目。算法的时间复杂度为 O ( N × M ) O(N \times M) O(N×M),能够高效应对输入规模较大的情况,而空间复杂度为 O ( N × M ) O(N \times M) O(N×M),主要用于存储矩阵和标记访问的状态。