acwing-194. 涂满它

考点

  • flood-fill优化

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10, dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
// v数组中,0代表未接触,1代表连通块,2代表连通块边缘
int n, g[maxn][maxn], v[maxn][maxn];

bool valid(int x, int y) { return x >= 1 && y >= 1 && x <= n && y <= n; }

// flood-fill更新v数组
void dfs(int x, int y, int c) {
v[x][y] = 1;
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i], ny = y + dy[i];
if (!valid(nx, ny)) continue;
if (v[nx][ny] == 1) continue;
if (g[nx][ny] == c)
dfs(nx, ny, c);
else
v[nx][ny] = 2;
}
}

// 估价函数,连通块之外有几种待处理颜色
int f() {
int t[6], res = 0;
memset(t, 0, sizeof(t));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (v[i][j] != 1) t[g[i][j]] = 1;
}
}
for (int i = 0; i < 6; ++i) {
if (t[i]) ++res;
}
return res;
}

// 寻找连通块边缘中可以扩展的部分
bool dye(int c) {
bool flag = false;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (g[i][j] == c && v[i][j] == 2) {
dfs(i, j, c);
flag = true;
}
}
}
return flag;
}

bool ida(int depth, int mxdepth) {
if (depth + f() > mxdepth) return false;
if (!f()) return true;
int backup[maxn][maxn];
memcpy(backup, v, sizeof(v));
for (int k = 0; k < 6; ++k) {
if (!dye(k)) continue;
if (ida(depth + 1, mxdepth)) return true;
memcpy(v, backup, sizeof(v));
}
return false;
}

int main() {
while (cin >> n && n) {
memset(v, 0, sizeof(v));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
cin >> g[i][j];
}
}
int depth = 0;
dfs(1, 1, g[1][1]);
while (!ida(0, depth)) ++depth;
cout << depth << endl;
}
return 0;
}

思路

flood-fill操作可以使用bfs或者dfs实现,但是枚举左上角的颜色只能DFS,且要找最小步数,考虑IDAstar。

估价函数很简单,由于每次最多处理一种颜色,所以未来最少步数等于连通块之外有几种待处理颜色

这里除了估价函数之外没有额外的剪枝,重点在于flood-fill操作常数的优化;每次都从左上角重头开始会超时。

如图所示,设有点(i, j),标志数组v[i][j]存储状态,颜色数组g[i][j]存储颜色,

状态分为:

  • 等于1代表是连通块
  • 等于2代表是连通块边缘
  • 等于0代表未访问

如果当前左上角选择了颜色c,那么只需要找v[i][j] == 2 && g[i][j] == c的格子进行flood-fill扩展即可,

在每个格子的扩展过程中:

  • 如果邻近格子v[x][y] != 1 && g[x][y] == c,令v[x][y] = 1设为连通块,并递归扩展点(x, y)
  • 否则说明该点是连通块边缘,令v[x][y] = 2