/* 收获: 1. 概念理解 图形的拓扑等价 可见: http://www.baike.com/wiki/%E6%8B%93%E6%89%91 摘取重点: 在拓扑学里不讨论两个图形全等的概念,但是讨论拓扑等价的概念。比如,尽管圆和方形、三角形的形状、大小不同,在拓扑变换下,它们都是等价图形。在一个球面上任选一些点用不相交的线把它们连接起来,这样球面就被这些线分成许多块。在拓扑变换下,点、线、块的数目仍和原来的数目一样,这就是拓扑等价。一般地说,对于任意形状的闭曲面,只要不把曲面撕裂或割破,他的变换就是拓扑变换,就存在拓扑等价。 2. 这个博客的讲解比较清晰: http://www.cnblogs.com/acm1314/p/4534360.html 3. blog: http://blog.csdn.net/ecnu_lzj/article/details/71056490 这个博客,通过举例子,解释清楚了一个问题: 为什么上下左右,都要加上边界(这是为了,通过连通块的计算,来判断图里到底有几个洞) 不过最终,由于这道题还是不怎么会写,所以我自己没能写出来,而是看懂了小白书上的代码后,按照书的配套代码的思路敲的 */
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <set> #define rep(i, n) for (int i = 0; i < (n); i++) using namespace std; char bin[256][5]; const int maxh = 200 + 5; const int maxw = 50 * 4 + 5; int H, W, pic[maxh][maxw], color[maxh][maxw]; //col: color char line[maxw]; const int dx[] = { -1, 1, 0, 0 }; const int dy[] = { 0, 0, -1, 1 }; //遍历连通块时用,对应的是四个方向的坐标变化量 vector<set<int> > neighbours; const char code[] = "WAKJSD"; //通过黑连通块的序号,找到它内部的白连通块个数(即:象形文字内的洞的个数),最终返回洞数所对应的字母 char change(int c) { int cnt = neighbours[c].size(); return code[cnt]; } void print() //这个函数可以用来做测试,看十六进制转换为二进制以后,转换结果是否正确 { rep(i, H) { rep(j, W) printf("%d", pic[i][j]); printf(" "); } } void decode (char ch, int row, int col) { rep(i, 4) pic[row][col + i] = bin[ch][i] - '0'; //把每个十六进制字符,转为二进制时,一个字符变为对应变为四个字符 } bool isin(int x, int y) { return x >= 0 && x < H && y >= 0 && y < W; } bool isok(int x, int y) //判断 (x, y)是否在迷宫区域内,且尚未被染色 { return isin(x, y) && !color[x][y]; } //从位置(row, col)开始进行dfs,并将与之相邻的整个连通块,设置为颜色c void dfs(int row, int col, int c) { color[row][col] = c; rep(i, 4) { int row2 = row + dx[i]; int col2 = col + dy[i]; if (isok(row2, col2) && pic[row2][col2] == pic[row][col]) //判断对应位置二进制数码是否一致 dfs(row2, col2, c); } } void check_neighbours(int row, int col) { rep(i, 4) { int row2 = row + dx[i], col2 = col + dy[i]; if (isin(row2, col2) && !pic[row2][col2] && color[row2][col2] != 1) neighbours[color[row][col]].insert(color[row2][col2]); //cnt为1的一圈(因为经过了color数组的赋值,所以也即为,color数组中,对应元素为1的圈) 其实是象形文字的最外圈,也就是我们加的四周边界,以及,四周边界和象形文字之间,还会有一些区域和四周边界连接,这整个的连通块,其实和象形文字本身无关,故而不做处理 } } int main() { strcpy(bin['0'], "0000"); //用来将十六进制转换为二进制,很巧妙的方法,就是先将十六进制对应的二进制字符存好,到时就可以直接使用了 strcpy(bin['1'], "0001"); strcpy(bin['2'], "0010"); strcpy(bin['3'], "0011"); strcpy(bin['4'], "0100"); strcpy(bin['5'], "0101"); strcpy(bin['6'], "0110"); strcpy(bin['7'], "0111"); strcpy(bin['8'], "1000"); strcpy(bin['9'], "1001"); strcpy(bin['a'], "1010"); strcpy(bin['b'], "1011"); strcpy(bin['c'], "1100"); strcpy(bin['d'], "1101"); strcpy(bin['e'], "1110"); strcpy(bin['f'], "1111"); int kase = 0; while (scanf("%d%d", &H, &W) == 2 && H) { memset(pic, 0, sizeof(pic)); rep(i, H) { scanf("%s", line); rep(j, W) decode (line[j], i + 1, j * 4 + 1); //将十六进制的输入,转换为其对应的二进制,因而每个char,将会转换为4个字符 } H += 2; W = W * 4 + 2; //上下左右方向,都要在最外层加边界。否则在统计连通块时,有可能会在特殊的图形分布处出错 int cnt = 0; vector<int> cc; //对应二进制数位为1的连通块 (connnected components) memset(color, 0, sizeof(color)); rep(i, H) rep(j, W) if (!color[i][j]) { dfs(i, j, ++cnt); if (pic[i][j] == 1) cc.push_back(cnt); //扫描整个区域,遍历找到所有连通块,并为同一连通块的格子,标上相同的编号。同时,把黑连通块存进 vector cc中 } neighbours.clear(); neighbours.resize(cnt + 1); //neighbours vector中,存放的是int型集合,所以最好先自己 resize设置大小 //之所以要 +1,是因为有 dfs(i, j, ++cnt); 使得cnt其实是从1开始的,而不是0;而cnt的意义,恰好是当前的黑色连通块个数,而这样的设定,和vector中的下标的意义是有区别的(毕竟前者从1开始,后者从0开始),这点在设置vector的大小时,要尤其注意 rep(i, H) rep(j, W) { if (pic[i][j] == 1) check_neighbours(i, j); //扫描整个图形中的黑点,并把该点旁边的白色连通块(白色连通块个数,就代表了洞数)存入 vector neighbours //vector中的下标,恰好是黑点对应的连通块的编号 } vector<char> ans; rep(i, cc.size()) ans.push_back(change(cc[i])); sort(ans.begin(), ans.end()); printf("Case %d: ", ++kase); rep(i, ans.size()) printf("%c", ans[i]); printf(" "); } return 0; }