POJ-画家问题

题目描述:有一个正方形的墙,由N*N个正方形的砖组成,其中一些砖是白色的,另外一些砖是黄色的。Bob是个画家,想把全部的砖都涂成黄色。但他的画笔不好使。当他用画笔涂画第(i, j)个位置的砖时, 位置(i-1, j)、 (i+1, j)、 (i, j-1)、 (i, j+1)上的砖都会改变颜色。请你帮助Bob计算出最少需要涂画多少块砖,才能使所有砖的颜色都变成黄色。

输入:第一行是一个整数n (1≤n ≤15),表示墙的大小。接下来的n行表示墙的初始状态。每一行包含n个字符。第i行的第j个字符表示位于位置(i,j)上的砖的颜色。“w”表示白砖,“y”表示黄砖。

输出:一行,如果Bob能够将所有的砖都涂成黄色,则输出最少需要涂画的砖数,否则输出“inf”

样例输入:

5

wwwww

wwwww

wwwww

wwwww

wwwww

输出

15

OJ上功能运行正常但运行时间990ms左右,压线超时。所以把思路和代码放在本文里,以后再回来改。

这是典型的熄灯问题,大体沿用模板,也有一些改进。

思路如下:

       开辟两个二维数组,一个用来记录涂色情况,一个用来记录色块的颜色情况。对第一行进行枚举,总共有2^n种情况。对于列出的每一种第一行情况依次向下涂色,如果上一行经过涂色后呈现白色,则下一行的此列位置应该涂色。按照此规则向下直到最后一行。如果最后一行经过涂色后有一个白色则说明第一行的枚举情况不对,所以需要重新枚举下一个第一行情况。 

// 白色为1黄色为0
// 涂色为1不涂为0

输入:

cin >> nsize;
	char ch;
	for (int i = 1; i <= nsize; ++i)
		for (int j = 1; j <= nsize; ++j)
		{
			cin >> ch;
			color[i][j] = (ch == 'w' ? 1 : 0);
		}

枚举函数:

bool enumerate()  // 枚举第一行涂色的所有情况
{
	int c;
	// 第一种枚举的情况就是全零
	int time = 0;
	while (guess() == false)  // 如果此情况不成功
	{
		paint[1][1]++;
		c = 1;
		while (paint[1][c] > 1 && c < nsize)
		{
			paint[1][c] = 0;
			c++;
			paint[1][c]++;
		}
		time++;
	}
	if (time == (int)pow(2, nsize))
		return false;
	else
		return true;
}

注意第二个while循环的判断条件里需要加一个c的范围,毕竟二维数组多开了两列,所以需要限定在nsize内,否则会出错。且第二个while循环内是把第一行的所有情况当作二进制数来看待,逢二进一,time是循环的次数,如果循环了2^n次说明结果是inf

判断每一种枚举类型是否满足条件的guess函数:

bool guess()
{
	for (int i = 1; i < nsize; ++i)
		for (int j = 1; j <= nsize; ++j)
			paint[i + 1][j] = (color[i][j] + paint[i - 1][j]
				+ paint[i][j - 1] + paint[i][j + 1] + paint[i][j]) % 2;
	for (int j = 1; j <= nsize; ++j)
		if (color[nsize][j] != (paint[nsize - 1][j] +
			paint[nsize][j - 1] + paint[nsize][j + 1] + paint[nsize][j]) % 2)
			return false;
	return true;
}

对于其中的取余表达式:

第一个:i行j列的color为1(白色),且该色块左、右、上、自己四个色块都为0(表示都没有经过涂色)时,i+1行j列的该色块就应该涂色。一旦左、右、上、自己四个色块有一个色块涂了色则i+1行j列的该色块就不涂色。所以是加上四个paint元素后的和%2

第二个:最后一行的paint元素也定下来后,需要判断最后一行的色块和倒数第二行的色块涂完色后最后一行是否有白色。右式的结果为1时表示经过四个色块的作用后,nsize行j列的色块应该被涂色,而此时如果该色块的颜色为0(黄色),它再经过涂色后就不再是黄色,所以return false。这种判断个人认为有点绕,不过毕竟中间的符号要么是==要么是!=,可以直接假设一种情况带入看是否满足,这样思路会简单一些。

完整代码如下:

#include <iostream>
#include <math.h>
using namespace std;
// 白色为1黄色为0
// 涂色为1不涂为0
int color[16][17] = { 0 };
int paint[16][17] = { 0 };
int nsize;

bool guess()
{
	for (int i = 1; i < nsize; ++i)
		for (int j = 1; j <= nsize; ++j)
			paint[i + 1][j] = (color[i][j] + paint[i - 1][j]
				+ paint[i][j - 1] + paint[i][j + 1] + paint[i][j]) % 2;
	for (int j = 1; j <= nsize; ++j)
		if (color[nsize][j] != (paint[nsize - 1][j] +
			paint[nsize][j - 1] + paint[nsize][j + 1] + paint[nsize][j]) % 2)
			return false;
	return true;
}bool enumerate()  // 枚举第一行涂色的所有情况
{
	int c;
	// 第一种枚举的情况就是全零
	int time = 0;
	while (guess() == false)  // 如果此情况不成功
	{
		paint[1][1]++;
		c = 1;
		while (paint[1][c] > 1 && c < nsize)
		{
			paint[1][c] = 0;
			c++;
			paint[1][c]++;
		}
		time++;
	}
	if (time == (int)pow(2, nsize))
		return false;
	else
		return true;
}
int main()
{
	cin >> nsize;
	char ch;
	for (int i = 1; i <= nsize; ++i)
		for (int j = 1; j <= nsize; ++j)
		{
			cin >> ch;
			color[i][j] = (ch == 'w' ? 1 : 0);
		}
	bool flag=enumerate();
	int result = 0;
	for (int i = 1; i <= nsize; ++i)
		for (int j = 1; j <= nsize; ++j)
			if (paint[i][j])
				result += 1;
	if (flag)
		cout << result;
	else
		cout << "inf";
	return 0;
}

2022年3月26日

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值