例题三 熄灯问题
描述
有一个由按钮组成的矩阵,其中每行有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
思路与分析
- 一盏灯最多只能被按动一次,按动两次就等于没按
- 各个按钮按下的顺序对结果莫得影响
- 我们可以通过枚举所有可能的开关状态,对每个状态计算最后灯的状态,看是否都熄灭。但是我们研究发现,只要知道第一行的灯的状态(由第一行灯的初始状态和第一行开关的状态决定),要熄灭这一行的所有灯,第二行的开关的状态就是唯一确定的。故而我们只要讨论第一行的灯的开关状态,结合第一行的灯的原本状态,就可以唯一推断出最后的状态,只靠观察最后一行(第6行)是否所有的灯都熄灭即可
- 故而我们只需要枚举第一行的状态,共有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;
}