Bootstrap

熄灯问题

例题三 熄灯问题
描述

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

输入

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。

0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。

输出

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。

其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

样例输入

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

样例输出

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

思路与分析
  1. 一盏灯最多只能被按动一次,按动两次就等于没按
  2. 各个按钮按下的顺序对结果莫得影响
  3. 我们可以通过枚举所有可能的开关状态,对每个状态计算最后灯的状态,看是否都熄灭。但是我们研究发现,只要知道第一行的灯的状态(由第一行灯的初始状态和第一行开关的状态决定),要熄灭这一行的所有灯,第二行的开关的状态就是唯一确定的。故而我们只要讨论第一行的灯的开关状态,结合第一行的灯的原本状态,就可以唯一推断出最后的状态,只靠观察最后一行(第6行)是否所有的灯都熄灭即可
  4. 故而我们只需要枚举第一行的状态,共有64种。考虑到每一行都是由0、1组成,我们可以直接用一个字符来表示这个行的状态(一个字符有8个比特),结合位运算符即可完成各种操作
代码实现
#include<iostream>
using namespace std;
void SetBit(char& c, int i, int v)
{
	if (v)c |= (1 << i);
	else c &= ~(1 << i);
}
int GetBit(char c, int i)
{
	return 1&(c>>i);
}
void FlipBit(char& c, int i)
{
	c ^= (1 << i);
}
void print(char results[])
{
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 6; j++)
		{
			if (j)cout << " ";
			cout << GetBit(results[i], j);
		}
		cout << endl;
	}
}
void close()
{
	char orilights[6] = { 0 };
	char switches;
	int temp = 0;
	for (int i = 0; i < 5; i++)//original settings
		for (int j = 0; j < 6; j++)
		{
			cin >> temp;
			SetBit(orilights[i], j, temp);
		}
	for (int n = 0; n < 64; n++)//n : switches for line 1
	{
		char results[6] = { 0 }, lights[6] = { 0 };
		for (int i = 0; i < 5; i++)lights[i] = orilights[i];
		switches = n;
		for (int e = 0; e < 5; e++)
		{
			for (int i = 0; i < 6; i++)
			{
				if (GetBit(switches, i))
				{
					FlipBit(lights[e], i);
					if (i >= 1)FlipBit(lights[e], i - 1);
					if (i <= 4)FlipBit(lights[e], i + 1);
					if (e <= 3)FlipBit(lights[e + 1], i);
				}
			}
			results[e] = switches;
			switches = lights[e];
		}
		if (lights[4] == 0)
		{
			print(results);
			break;
		}
	}
	return;
}
int main()
{
	close();
	return 0;
}
;