UVA - 1103 Ancient Messages

/*
  收获:
  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("\n");
	}
}

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("\n");
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值