POJ1222熄灯问题 枚举算法 经典的位运算应用

题目:POJ1222熄灯问题

题目描述:有一个由按钮组成的矩阵,其中每行有6个按钮,共5行,每个按钮的位置上有一盏灯,当按下一个按钮后,该按钮以及周围位置(上边,下边,左边,右边)的灯都会改变状态。如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。给定矩阵中每盏灯的初始状态,求一种按按钮方案,使得所有的灯都熄灭。

●输入样例
2
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0
0 0 1 0 1 0
1 0 1 0 1 1
0 0 1 0 1 1
1 0 1 1 0 0
0 1 0 1 0 0

●输出样例
PUZZLE #1
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0
PUZZLE #2
1 0 0 1 1 1
1 1 0 0 0 0
0 0 0 1 0 0
1 1 0 1 0 1
1 0 1 1 0 1

题解:
每个位置不会开关超过一次,因此每个位置值为0或者1,并且开关的顺序无关。
采用枚举法,但是枚举每个位置一共2^30次,是不可行的。考虑枚举局部,当局部确定了之后,整体的结果也是唯一的。显然,第一行就是符合这个条件的局部。a[0][j]和a[1][j]是唯一确定的。
试过用数组去实现,非常麻烦。郭炜老师是用***char字符存储、结合位运算实现开关的操作***。

关键点如下
1.枚举第一行2^6种开关操作,可以用0-63这64个数表示,其存储的后六个比特就是01代表的开关操作,不用写6层循环枚举操作。
2.存储:char originLinghts[5];存储输入的灯的状态,循环地调用void SetBit(char & c, int i, int v)函数将右边第i位置为1或0。注意函数的参数c是引用,因为要改变它。位的设置用的是位运算,将右边第位设为1时,只需先把整型的1左移位,再和c做或运算;类似的,设为0时,只需将整型1左移位并取反,再和c做与运算。
3.枚举的时候,每种情况操作switchs都是对第一行灯的操作。如果switchs的右边第j位是0,则没有影响;若是1,则首先翻转第一行右边第j个位置,及其左右两个的位,注意边界,其次改变下一行的灯的状态。其中,翻转右边第j位调用void ReverseBit(char & c, int i)函数,整数1左移i位再和c做异或,改变下一行的状态是用下一行和switchs直接异或操作。做完这些后,第一行的状态,必须成为下一行的switchs,因为必须把第一行开的灯灭掉,那么这个操作也意味着操作一行的时候,只需要考虑当前行和下一行的改变,上一行不用考虑。
4.找到结果的标志是,最后一行灯全灭,因为第一行的状态必须成为下一行的switchs这一条件已经可以保证前面四行全灭。否则,就重新拷贝等的原始状态,做下一次循环。

程序如下:(这是北京大学郭炜老师在将算法时写的程序,char字符存储、结合位运算实现开关的操作,非常值得细看)

#include 
#include 
#include 
#include 

using namespace std;

char originLinghts[5];
char lights[5];
char result[5];

int GetBit(char c, int i)
{
	return (c >> i) & 1;
}

void SetBit(char & c, int i, int v)
{
	if (v)
		c |= (1 << i);
	else
		c &= ~(1 << i);
}

void ReverseBit(char & c, int i)
{
	c ^= (1 << i);
}

void OutputResult(int n, char result[])
{
	cout << "PUZZLE #" << n << endl;
	for (int i = 0; i < 5; i++) {
		for (int j = 0; j < 6; j++) {
			cout << GetBit(result[i], j);
			if (j < 5)
				cout << " ";
		}
		cout << endl;
	}

}

int main()
{
	int N;
	cin >> N;
	for (int n = 0; n < N; ++n) {
		for (int i = 0; i < 5; ++i) {
			for (int j = 0; j < 6; ++j) {
				int tmp;
				cin >> tmp;
				SetBit(originLinghts[i], j, tmp);
			}
		}
		for (int t = 0; t < 64; ++t) {
			int switchs = t;
			memcpy(lights, originLinghts, 5);
			for (int i = 0; i < 5; ++i) {
				result[i] = switchs;
				for (int j = 0; j < 6; ++j) {
					if (GetBit(switchs, j)) {
						ReverseBit(lights[i], j);
						if(j > 0)
							ReverseBit(lights[i], j - 1);
						if (j < 5)
							ReverseBit(lights[i], j + 1);
					}	
				}
				if (i < 4)
					lights[i + 1] ^= switchs;
				switchs = lights[i];
			}
			if (lights[4] == 0) {
				OutputResult(n + 1, result);
				break;
			}
		}
	}
	system("pause");
	return 0;
}

你可能感兴趣的:(POJ1222熄灯问题 枚举算法 经典的位运算应用)