按照《算法竞赛入门经典》的思路,我们只需要判断出当前文字有几个“洞”,就可以确定到底是哪个文字了。
W:零个洞
A:一个洞
K:两个洞
J:三个洞
S:四个洞
D:五个洞
本题重点就是如何判断一个文字有几个洞。我们知道每一个文字都是由很多个 "1" 组成的,背景是 “0”。
请看下面这个文字:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
虽然看的眼花,但还是很容易就看出来这是文字 A,因为它只有一个洞。我们用程序该怎么判断呢?
相信读者都会用 dfs 判连通块,并且给连通块染色吧!
先用 dfs 把上图的连通块找出来,并染色。假设我们用了三种颜色分别是1, 2, 3,那么染色之后的图片就变成了:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1
1 1 1 1 2 2 2 2 3 3 3 3 2 2 2 2 1 1 1 1
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 2 2 2 2 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 2 2 2 2 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
现在有两张图片,一张未染色,一张已染色。
现在我们在未染色的图片中逐一检查 “1” 元素(从染色图片中可以看出来,所有的 “1” 元素都是颜色 2),看它上下左右四个方向有没有 “0” 元素,如果有的话,假设当前 “0” 元素的颜色是1,我们把颜色 1 放到一个集合 set 中。
按照这样的方法,检查完所有的 “1” 之后,集合 set 中就有两个元素了,分别是 1,3。
这样我们就可以知道颜色为 2 的连通块邻接着颜色为 1 和颜色为 3 的连通块,也就是颜色为 2 的连通块有 1 个洞!通过这样的方法我们就可以判断一个文字有几个洞了。
因为实际中会有多个文字,所以我们要创建一个元素是 set 的 vector。
假如这个 vector 的名字叫做 adjacent,那么adjacent[i] 就表示和颜色为 i 的连通块邻接的连通块的颜色的集合。
当我们在检查颜色为 i 的 “1” 元素时,如果在上下左右四个方向找到了一个 “0” 元素,就将这个 “0” 元素的颜色放到 adjacent[i] 中去。
这道题的大致思路就是如此。不过在将16进制数字转化成2进制数字的时候也用了一个巧妙的方法,具体看代码吧,相信仔细看可以看懂的!
ps:在输入过程中要将图片拓展,上下左右各添加一行。至于为什么?看了下面这组数据你就懂了。
0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0
0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0
代码如下:
#include <set>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long int ll;
const char* Hexadecimal[16] = {"0000", "0001", "0010", "0011",
"0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011",
"1100", "1101", "1110", "1111"};
int pic[205][205], color[205][205];
char table[10] = "*WAKJSD";
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int H, W;
// 判断坐标是否越界
bool is_legal(int x, int y)
{
return x >= 0 && x < H && y >= 0 && y < W;
}
// dfs染色
void dfs(int x, int y, int c)
{
color[x][y] = c;
for (int d = 0; d < 4; d++)
{
int xx = x + dx[d], yy = y + dy[d];
if (is_legal(xx, yy) && pic[xx][yy] == pic[x][y] && !color[xx][yy])
dfs(xx, yy, c);
}
}
int main()
{
//freopen("1.txt", "r", stdin);
int Case = 1;
while (~scanf("%d%d", &H, &W) && H && W)
{
memset(pic, 0, sizeof(pic));
memset(color, 0, sizeof(color));
// 输入过程中将图片上下左右额外拓展了一行“0”
for (int i = 1; i <= H; i++)
{
getchar();
for (int j = 1; j <= W; j++)
{
char c = getchar();
for (int k = 0; k < 4; k++)
{
if (c >= '0' && c <= '9')
pic[i][k + (j - 1) * 4 + 1] = Hexadecimal[c - '0'][k] - '0';
else pic[i][k + (j - 1) * 4 + 1] = Hexadecimal[c - 'a' + 10][k] - '0';
}
}
}
W = W * 4 + 2;
H += 2;
int cnt = 1; // 不能设为0,否则第一个连通块会被认为没染色
vector<int> BlackNumber; // 保存着黑色连通块的编号
for (int i = 0; i < H; i++)
for (int j = 0; j < W; j++)
{
if (!color[i][j])
{
dfs(i, j, cnt++);
if (pic[i][j])
BlackNumber.push_back(cnt - 1);
}
}
vector<set<int>> adjacent(205);
for (int i = 0; i < H; i++)
for (int j = 0; j < W; j++)
{
if (pic[i][j])
{
for (int d = 0; d < 4; d++)
{
int x = i + dx[d], y = j + dy[d];
if (is_legal(x, y) && !pic[x][y])
adjacent[color[i][j]].insert(color[x][y]); // 将相邻连通块的颜色都放到set中去
}
}
}
vector<char> ans;
for (unsigned int i = 0; i < BlackNumber.size(); i++)
{
int c = BlackNumber[i];
int t = adjacent[c].size();
ans.push_back(table[t]);
}
sort(ans.begin(), ans.end());
printf("Case %d: ", Case++);
for (unsigned int i = 0; i < ans.size(); i++)
cout << ans[i];
cout << endl;
}
return 0;
}