填涂颜色
题目描述
由数字 $0$ 组成的方阵中,有一任意形状的由数字 $1$ 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 $2$。例如:$6\times 6$ 的方阵($n=6$),涂色前和涂色后的方阵如下:
如果从某个 $0$ 出发,只向上下左右 $4$ 个方向移动且仅经过其他 $0$ 的情况下,无法到达方阵的边界,就认为这个 $0$ 在闭合圈内。闭合圈不一定是环形的,可以是任意形状,但保证闭合圈内的 $0$ 是连通的(两两之间可以相互到达)。
0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 1 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 1 2 1
1 1 1 1 1 1
输入格式
每组测试数据第一行一个整数 $n(1 \le n \le 30)$。
接下来 $n$ 行,由 $0$ 和 $1$ 组成的 $n \times n$ 的方阵。
方阵内只有一个闭合圈,圈内至少有一个 $0$。
输出格式
已经填好数字 $2$ 的完整方阵。
样例 #1
样例输入 #1
6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
样例输出 #1
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1
提示
对于 $100\%$ 的数据,$1 \le n \le 30$。
思路:题目描述是一个关于连通块的问题,图中只存在0 和 1,但是不能很好的判断哪一个‘0’的连通块在‘1’的内部。根据题目只会有一个‘1’的连通块,所以不用考虑圆环的情况,并且在‘1’连通块内部的‘0’连通块不会与边界接触,即连通块点的坐标不会超出边界,所以在求连通块时,可以用一个数组判断当前连通块是否会接触到边界
。
#include <iostream>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 35, M = N * N;
int n;
int g[N][N];
PII q[M];
int st[N][N]; // 记录每个点属于哪个连通块
int cnt; // 记录连通块数量
bool in[M]; // 判断当前连通块是否符合条件
void bfs(int sx, int sy)
{
cnt++;
int hh = 0, tt = -1;
q[++tt] = { sx, sy };
st[sx][sy] = cnt;
int dx[4] = { 0, -1, 0, 1 }, dy[4] = { -1, 0, 1, 0 };
while (hh <= tt)
{
PII t = q[hh++];
for (int i = 0; i < 4; i++)
{
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= n)
{
in[cnt] = false;
continue;
}
if (st[a][b]) continue;
if (g[a][b] != g[t.x][t.y]) continue;
q[++tt] = { a, b };
st[a][b] = cnt;
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &g[i][j]);
memset(in, true, sizeof in);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (!st[i][j] && g[i][j] == 0)
bfs(i, j);
for (int i = 1; i <= cnt; i ++ )
if (in[i])
{
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (st[j][k] == i)
g[j][k] = 2;
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
printf("%d ", g[i][j]);
puts("");
}
return 0;
}
思路二:可以将“在1的闭合圈内部的0变为2” 转变为 “先将全部0变为2,再找到1外部的2变为0”。但如果直接从起点(1,1)开始找,可能1外部的2不是全部连通的,会被1打断,比如
2 2 1 2 2 2
2 1 2 1 1 1
2 1 2 2 2 1
1 1 2 2 2 1
1 2 2 1 2 1
1 1 1 1 1 1
外部0被拆分成2个部分,所以可以在图的外层再套一层2,变为:
2 2 2 2 2 2 2 2
2 2 2 1 2 2 2 2
2 2 1 2 1 1 1 2
2 2 1 2 2 2 1 2
2 1 1 2 2 2 1 2
2 1 2 2 1 2 1 2
2 1 1 1 1 1 1 2
2 2 2 2 2 2 2 2
这样就把1外部的2全部连接在了一起,然后再bfs(0, 0)找到1外部的连通块,全部变为0
#include <iostream>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 35, M = N * N;
int n;
int g[N][N];
PII q[M];
bool st[N][N];
void bfs()
{
int hh = 0, tt = -1;
q[++tt] = { 0, 0 };
st[0][0] = true;
int dx[4] = { 0, -1, 0, 1 }, dy[4] = { -1, 0, 1, 0 };
while (hh <= tt)
{
PII t = q[hh++];
for (int i = 0; i < 4; i++)
{
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a > n + 1 || b < 0 || b > n + 1) continue;
if (st[a][b]) continue;
if (g[a][b] == 1) continue;
q[++tt] = { a, b };
st[a][b] = true;
g[a][b] = 0;
}
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
scanf("%d", &g[i][j]);
if (g[i][j] == 0) g[i][j] = 2; // 将所有的0先变为2
}
//在矩阵外面再包一层2,使在圈外的2连接起来
for (int i = 0; i <= n + 1; i++) g[0][i] = g[n + 1][i] = g[i][0] = g[i][n + 1] = 2;
//从(0, 0)开始找连通块,这一部分就是在圈外的2,将他们全部变为0
bfs();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
printf("%d ", g[i][j]);
puts("");
}
return 0;
}