UVA-1602 网格动物 题解答案代码 算法竞赛入门经典第二版

GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版

使用dfs遍历所有情况,再去重即可。

遍历部分

遍历的时候需要注意:

1. 这其实并不能算是dfs,因为普通的dfs是一直走到尽头,然后再逐渐退出,走下一条路。到最后的路径是一条线。但是这个题目生成的图形却可以分叉。因此,递归一次dfs时,要循环遍历当前所有的节点,让所有的节点都可以分叉进行。

2. 如果直接遍历所有情况,那么肯定是超时的。我是这样做的:

我一开始尝试了从w*h区域的每个点开始遍历,遇到墙就返回。这样其实存在大量的重复,会重复生成一样的图形。我后面改造成了直接从(1, 1)点开始遍历。并不真实的设立一个墙。在生成的过程中,如果碰到这个图像的宽和高大于区域的大小,那么认为这个区域放不下这个图形,就退出。这中间出现坐标负值也是正常的。

3. 用上面的方法之后,还是有较多的超时。因此我们继续优化。这时候我们设定一个中间图形是否遍历过的set。假设该图形或者该图形的旋转,平移和镜像在set中存在了,就说明已经遍历过了,就不在往下进行。这样能够很大的缩小情况的种类。比如:[(1,1), (1,2)] 和 [(1,1), (2,1)] 等等都属于重复图形,也就是说,dfs的第二层依旧只有一个结点。这样节约的时间是很多的。

4. 用了上面的方法后,还是超时。但是即使输出全部的情况下,在本地电脑中也是比较快的,大概一两分钟左右。由于情况不多(不超过1000中情况),因此直接打表更适合。即我们在本地算出结果,把结果直接写在代码里。我直接在代码中写了n=10情况下的所有输出,其他情况下还是实时计算的。这样最后AC了。

如何对图形进行的旋转,平移和镜像

除了遍历之外,还有一个注意的点就是,如何对图形进行的旋转,平移和镜像。且能在set中标识这个图形。

1. 标识图形

由于每个点的的最大坐标为(1-10,1-10),最多10个点,因此标识所有情况需要 10^20。64位存不下。换一种表示法,用起点加转向,就是100*4^9, 64位存下绰绰有余。但是我们并不是一条路径,可以有分叉,因此这种方式做不到。

还有一个问题就是,存在set中的结构要保证唯一性,即同样一个图形,如果点的位置不一致,也可以找到。这时候我们就想到用set存储单个图形。然后单个图形的set元素是一个int,里面的值是100 * x + y。由于set是排好序的,因此我们的点在里面就是天然排序的,先按照x值排序,在按照y值排序。这样可以保证点不会因为位置交换了而判断为两个图形。

当然,100 * x + y的方法在遇到负值是有很大的问题,后面我又进行了改进。网络上还有人用其他方法可以存储单个点的,也可以参考。假设我们的点是(1, -1),那么值是-99。还原回点的时候,就变成了(0, -99)。

我的解决方案是,让值保持整数: 100 * x + y  ——>  100 *(x+20) + y + 20。由于坐标最大为10,因此计算的结果肯定是正的,还原回来也肯定是正的。

2. 平移

平移非常简单,我们让以x坐标和y坐标最小值为基准,设为1。其他的点平移这个差值即可。

这个平移的作用实际上是标准化,即将在不同位置的图形,都平移到一个位置,如果他是一个图形,那么平移之后位置是一致的。

3. 旋转图形

我们只需要实现转90度的方法即可。转两次就是180,三次就是270。

向右转90度,对于图形来说,坐标变化就是:(x, y) -> (y, -x)。画个图试一下就能看出规律了。因此,每次旋转时我们执行这个变化即可。

旋转之后还要平移一次。

4. 镜像图形

我们随便横向镜像和纵向镜像都可以,因此横向镜像后转90度就变为纵向镜像了。镜像更简单,x或者y的符号变一下即可。这时候我们注意到一点:

如果我们的点坐标是0,镜像完还是0,那肯定是不正确的。因此坐标从1开始,即1 - 10。1的前一个点是-1,我们没有0这个点。这时候平移和遍历就出问题了:

1和-1中间的差值是2不是1,我们如果正常遍历, 会走到0这个点。我们平移的时候如果图形中间穿过0这条线,那么减的值是不正确的。因此,我们要相应做处理,如果坐标值碰到0,根据情况设为1或者-1。平移的时候区分中间有0和没有0的情况。

AC代码

#include
#include
#include
#include

using namespace std;

int n, w, h;
int steps[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
// 数量
int num;
set seArr, seTemp;
set> se;
int arrTemp[10][2];
// 该状态是否访问过 
set> seFlag;

int tenTable[1000] = {0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,16,68,108,117,118,0,0,0,21,277,842,1226,1329,1338,1339,0,0,21,287,1847,3234,3773,3876,3885,3886,0,1,277,1847,2376,4003,4542,4645,4654,4655,0,16,842,3234,4003,4003,4542,4645,4654,4655,0,68,1226,3773,4542,4542,4542,4645,4654,4655,0,108,1329,3876,4645,4645,4645,4645,4654,4655,0,117,1338,3885,4654,4654,4654,4654,4654,4655,1,118,1339,3886,4655,4655,4655,4655,4655,4655};

void getXY(int res, int &x, int &y) {
	x = res / 100 - 20;
	y = res % 100 - 20;
}

void output(set &setTemp) {
	int x, y;
	for(auto ip = setTemp.begin(); ip != setTemp.end(); ++ip) {
		getXY(*ip, x, y);
		printf("[%d,%d] ", x, y);
	}
	putchar('\n');
}

int setXY(int x, int y) {
	return (x + 20) * 100 + y + 20;
}

// 规范化 最小的x为 1 最小的y为 1
void standard(int (* arr)[2], int ntemp) {
	int i;
	int minX = arr[0][0], minY = arr[0][1];
	for(i = 1; i < ntemp; ++i) {
		if(arr[i][0] < minX) minX = arr[i][0];
		if(arr[i][1] < minY) minY = arr[i][1];
	}
	for(i = 0; i < ntemp; ++i) {
		if(arr[i][0] * minX > 0)
			arr[i][0] = arr[i][0] - minX + 1;
		else
			arr[i][0] = arr[i][0] - minX;
		if(arr[i][1] * minY > 0)
			arr[i][1] = arr[i][1] - minY + 1;
		else
			arr[i][1] = arr[i][1] - minY;
	}
}

// 点的数组转为set 
void arr2Set(set &setTemp, int (* arr)[2], int ntemp) {
	setTemp.clear();
	for(int i = 0; i < ntemp; ++i) {
		setTemp.insert(setXY(arr[i][0], arr[i][1]));
	}
}

// set转为点的数组
void set2Arr(set &setTemp, int (* arr)[2]) {
	int i = 0;
	for(auto ip = setTemp.begin(); ip != setTemp.end(); ++ip, ++i) {
		getXY(*ip, arr[i][0], arr[i][1]);
	}
}

// 向右旋转90度
void turnRight(int (* arr)[2], int ntemp) {
	int j;
	for(int i = 0; i < ntemp; ++i) {
		j = arr[i][1];
		arr[i][1] = -arr[i][0];
		arr[i][0] = j;
	}
}

// 镜像反转
void reserveArr(int (* arr)[2], int ntemp) {
	for(int i = 0; i < ntemp; ++i) {
		arr[i][0] = -arr[i][0]; 
	}
}

void judge(set &setTemp) {
	if(se.count(setTemp)) return;
	++num;
	se.insert(seTemp);
}

bool isBumpWall(set &setTemp) {
	auto ip = setTemp.begin();
	int x, y;
	getXY(*ip, x, y);
	int minX = x, minY = y, maxX = x, maxY = y;
	for(ip = setTemp.begin(); ip != setTemp.end(); ++ip) {
		getXY(*ip, x, y);
		if(x < minX) minX = x;
		if(y < minY) minY = y;
		if(x > maxX) maxX = x;
		if(y > maxY) maxY = y;
	}
	x = maxX - minX - (maxX * minX < 0 ? 1 : 0);
	y = maxY - minY - (maxY * minY < 0 ? 1 : 0);
	if(((x >= w || y >= h) && (x >= h || y >= w)) || x + y >= n) return true;
	return false; 
} 

// 该模式的几种变形是否访问过 
bool findModel(int (* arr)[2], set &setTemp, int ntemp) {
	if(seFlag.count(setTemp)) return true;
	int i;
	for(i = 0; i < 3; ++i) {
		// 转90度
		turnRight(arrTemp, ntemp);
		standard(arrTemp, ntemp);
		arr2Set(setTemp, arrTemp, ntemp);
		if(seFlag.count(setTemp)) return true;
	}
	// 镜像反转
	reserveArr(arrTemp, ntemp);
	for(i = 0; i < 4; ++i) {
		// 转90度
		if(i != 0) turnRight(arrTemp, ntemp);
		standard(arrTemp, ntemp);
		arr2Set(setTemp, arrTemp, ntemp);
		if(seFlag.count(setTemp)) return true;
	}
	return false; 
}

void dfs(int count) {
	++count;
	int i, x, y, xi, yi, resi;
	for(auto ip = seArr.begin(); ip != seArr.end(); ++ip) {
		getXY(*ip, x, y);
		for(i = 0; i < 4; ++i) {
			xi = steps[i][0] + x;
			yi = steps[i][1] + y;
			if(xi == 0) {
				if(steps[i][0] > 0) xi = 1;
				if(steps[i][0] < 0) xi = -1;
			}
			if(yi == 0) {
				if(steps[i][1] > 0) yi = 1;
				if(steps[i][1] < 0) yi = -1;
			}
			resi = setXY(xi, yi);
			if(seArr.count(resi)) continue;
			seArr.insert(resi);
			set2Arr(seArr, arrTemp);
			standard(arrTemp, count);
			arr2Set(seTemp, arrTemp, count);
			if(!isBumpWall(seArr) && !findModel(arrTemp, seTemp, count)) {
				// 设置访问标记 
				seFlag.insert(seTemp);
				if(count == n) judge(seTemp);
				else dfs(count);
			}
			seArr.erase(resi);
		}
	}
}

int compute() {
	num = 0;
	se.clear();
	seFlag.clear();
	seArr.clear();
	seArr.insert(setXY(1, 1));
	dfs(1);
	return num;
}

int main() {
	while(scanf("%d %d %d", &n, &w, &h) == 3) {
		if(w * h < n) {
			printf("0\n");
			continue;
		}
		if(w * h == n || n == 1) {
			printf("1\n");
			continue;
		}
		if(n == 10) {
			printf("%d\n", tenTable[(w-1)*10 + h-1]);
			continue;
		}
		printf("%d\n", compute());
	}
	return 0;
}

你可能感兴趣的:(算法竞赛入门经典,算法,c++,数据结构,算法竞赛入门经典,aoapc,dfs,Polyomino)