紫书刷题进行中,题解系列【GitHub|CSDN】
例题6-13 UVA1103 Ancient Messages(59行AC代码)
题目大意
给定6种象形字符及其对于的标记字母,现用16进制字符表示一张图像,以字典序输出图像中的所有象形字符。有以下简化约定:
- 字符仅包括给定的6种
- 每个图像至少包含一个象形字符
- 每个黑色像素均属于一个象形字符
- 字符间相互独立,即不相交,不包含
- 处于对角位置的像素也算相邻
- 图像可以拉伸
思路分析
一道十分有趣的题目,值得琢磨。通过以下三个核心问题来讨论解决思路
问题1:如何分辨不同象形字符?
回答1:图像内部的白洞个数
这里需要一点想象力和观察力,既然图像可以拉伸变换,因此必须要找到一个不变的特征量,仔细观察所给出的6个图像,可以发现每个图像内部所包含的白洞个数依次为:1,3,5,4,0,2
。正好6个数无重复值,因此可以用图像的内部的白洞个数来辨别字符
问题2:怎么判断白色块属于哪个黑色连通块内部?
回答2:单个拷贝
若一张图中仅有一个黑色连通块,那么它的白色连通块可轻易计算出来,但存在多个黑色连通块时,就无法直接判定每个黑色连通块内部的白色块个数了。因此,假设原图存于img1,遍历其中一个黑色连通块的同时,拷贝到img2,此时img2中只包含一个黑色连通块,即可轻易计数
注意:直接拷贝的方式存在一个bug
解决:给拷贝的img2四周增加一个白色像素框(上下左右均多加一个)
当图中仅含一个黑色连通块时,令黑色像素点与边界围成白色块,会导致白色块增加。因此,可在img2四周增加一圈白框,连通外围白色块,避免以上bug
算法设计
先将16进制字符串转换为4位的2进制字符串,可用bitset
快速实现
定义string img[205], img2[205];
分别表示原图像,加一圈白边的图像
枚举img中的黑色连通块,对于每个黑色连通块,dfs过程中同步拷贝到img2,拷贝完毕后,dfs计算img2中的白色块个数
- 对于8个方向可用方向向量或一个二重循环处理
- 映射转换关系可用map或哈希表实现,简化代码
AC代码(C++11,思维题,dfs)
#include<bits/stdc++.h>
using namespace std;
int H, W, num=0;
string img[205], img2[205], s; // 原图像,加一轮白边的图像
map<char, string> mp; // 16进制字符->4位的2进制字符串
char word[6] = {'W', 'A', 'K', 'J', 'S', 'D'}; // 象形字内部对应的白色块个数
void trans() { // 一位16进制字符[0,f]转为4位的二进制字符串
char c[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
for (int i = 0; i < 16; i ++) {
bitset<4> bt(i); // 10->二进制数值->二进制字符串
mp[c[i]] = bt.to_string();
}
}
void dfs(int x, int y, char tag) { // tag='0':遍历img的一个黑色连通块,并拷贝到img2;tag='0':计算img2的白色连通块个数
if (tag == '0') img[x][y] = '0';
if (tag == '0') img2[x+1][y+1] = '1'; // 白色边框
else img2[x][y] = '1';
int xx, yy, h, w;
for (int i = -1; i <= 1; i ++) { // 8个方向
for (int j = -1; j <= 1; j ++) {
if (i == 0 && j == 0) continue; // 不写也行,自身已经被赋值为1了
xx = x + i; yy = y + j;
h = (tag == '1') ? H+2 : H; w = (tag == '1') ? 4*W+2 : 4*W; // 边界控制
if (xx >= 0 && xx < h && yy >= 0 && yy < w) {
if (tag == '0' && img[xx][yy] == '1' || tag == '1' && img2[xx][yy] == '0') dfs(xx, yy, tag);
}
}
}
}
int main() {
trans(); // 16转二进制字符串初始化
while (cin >>H >>W && (H != 0 && W != 0)) {
for (int i = 0; i < H; i ++) {
cin >>s; img[i].clear();
for (int j = 0; j < s.size(); j ++) img[i].append(mp[s[j]]); // 转为2进制
}
vector<char> res; // 保存结果
for (int i = 0; i < H; i ++) {
for (int j = 0; j < 4*W; j ++) {
if (img[i][j] == '1') { // 发现一个黑色块
for (int k=0; k < H+2; k ++) img2[k] = string(4*W+2, '0'); // 初始化
dfs(i, j, '0'); // 拷贝连通块
int cnt=0; // 统计白色块个数
for (int i2 = 0; i2 < H+2; i2 ++) { // img2计算白色洞个数
for (int j2 = 0; j2 < 4*W+2; j2 ++) { // 注意长度和宽度
if (img2[i2][j2] == '0') {cnt++; dfs(i2, j2, '1');}
}
}
res.push_back(word[cnt-1]); // 存储对应象形字符结果
}
}
}
sort(res.begin(), res.end()); // 字典序排列
printf("Case %d: ", ++num);
for (auto c : res) printf("%c", c);
puts("");
}
return 0;
}