【题目链接】
【题目考点】
1. 枚举
2. 二进制
【解题思路】
经典的点灯游戏,有兴趣的同学可以实现这个游戏。
对1个位置点灯有2种操作,对2个位置点灯有4种操作。根据乘法原理,对30个位置的点灯操作种类数为 2 30 ≈ 1 0 9 > 1 0 7 2^{30}\approx 10^9>10^7 230≈109>107,如果要枚举所有可能的点灯操作种类,复杂度过高,不可行。
假设第一行所有位置的点灯操作都做完了,而第一行还有一些灯亮着,为了让这些灯熄灭,就必须在第二行相应位置点灯,而且其它位置不能点灯。
比如第一行点灯操作结束后,第一行灯的情况为0 1 0 0 1 0。在这以后不能再在第1行点灯了,那么为了让这一行灯全灭,只能在第2行第2列与第5列点灯,而且其它位置不能点灯。如果点了,会破坏第一行的情况。
因此,只要第1行的点灯操作确定了,自然就确定了第2行的点灯操作。为了使第2行的灯都灭,接下来第3行的点灯操作也确定了,依此类推,直到最后一行的点灯操作都是确定了的。最后一行点灯操作结束后,最后一行的灯未必都灭。如果最后都灭了,那么这套点灯操作就是我们要求的解。
具体过程如下:
- 枚举第1行的点灯操作。
- 此时第1行亮灯的位置,就是第2行需要点灯的位置。第2行点灯后第2行亮灯的位置就是第3行需要点灯的位置,。。。,直到完成第5行的点灯操作。
- 看第5行的灯是否都灭了,如果都灭了,输出记录点灯操作的矩阵。
第一行是6个灯,每盏灯只有亮暗两种情况,因此可以视为6位二进制数字。
将点灯矩阵的第一行视为高精度6位二进制数字,每次循环让这高精度6位二进制数字增1,就可以枚举到下一个二进制数字。枚举所有的6位二进制数字,即000000~111111,十进制下为0~63。
枚举第1行点灯操作的种类数为 2 6 2^6 26,针对每种第1行的点灯操作,确定整个点灯矩阵需要 4 ∗ 6 4*6 4∗6次遍历,整体复杂度很低。
类似地也可以枚举第1列的情况,而后确认各列的情况。
【题解代码】
解法1:枚举第1行
#include<bits/stdc++.h>
using namespace std;
#define N 10
int ori_mp[N][N], mp[N][N], lt[N][N];//ori_mp:初始灯的状态 mp:灯的状态 lt[i][j]为1:在(i,j)位置进行点灯操作
int dir[5][2] = {
{
0, 0}, {
0, 1}, {
0, -1}, {
-1, 0}, {
1, 0}};
void light(int sx, int sy)//在(sx, sy)点灯
{
for(int i = 0; i < 5; ++i)
{
int x = sx + dir[i][0], y = sy + dir[i][1];
if(x >= 1 && x <= 5 && y >= 1 && y <= 6)
mp[x][y] = !mp[x][y];//以(sx,sy)为中心的十字花位置亮暗变换
}
}
int main()
{
for(int i = 1; i <= 5; ++i)
for(int j = 1; j <= 6; ++j)
cin >> ori_mp[i][j];
for(int k = 0; k < 64; ++k)//枚举6位二进制数字,作为第一行的情况
{
memcpy(mp, ori_mp, sizeof(ori_mp));
for(int j = 1; j <= 6; ++j)//根据第1行点灯的情况进行点灯
{
if(lt[1][j] == 1)
light(1, j);
}
for(int i = 2; i <= 5; ++i)
{
for(int j = 1; j <= 6; ++j)
{
if(mp[i-1][j] == 1)//如果(i-1,j)灯亮了,在第i-1行不能操作的情况下,只能在第(i,j)位置点灯,让(i-1,j)的灯灭掉。
{
lt[i][j] = 1;
light(i, j);
}
else//如果(i-1,j)的灯是灭的,在(i,j)处不能点灯
lt[i][j] = 0;
}
}
bool isDim = true;//第5行是否都灭了
for(int j = 1; j <= 6; ++j)
{
if(mp[5][j] == 1)
isDim = false;
}
if(isDim)//如果都灭了,得到了要求的点灯的结果
{
for(int i = 1; i <= 5; ++i)
{
for(int j = 1; j <= 6; ++j)
cout << lt[i][j] << ' ';
cout << endl;
}
return 0;
}
int j = 1;
lt[1][j]++;//把lt[1]看做一个高精度二级制数字,该操作使lt[1]这个二进制数字加1
while(lt[1][j] == 2)
{
lt[1][j] = 0;
j++;
lt[1][j]++;
}
}
return 0;
}
解法2:枚举第1列
#include<bits/stdc++.h>
using namespace std;
#define N 10
int ori_mp[N][N], mp[N][N], lt[N][N];//ori_mp:初始灯的状态 mp:灯的状态 lt[i][j]为1:在(i,j)位置进行点灯操作
int dir[5][2] = {
{
0, 0}, {
0, 1}, {
0, -1}, {
-1, 0}, {
1, 0}};
void light(int sx, int sy)//在(sx, sy)点灯
{
for(int i = 0; i < 5; ++i)
{
int x = sx + dir[i][0], y = sy + dir[i][1];
if(x >= 1 && x <= 5 && y >= 1 && y <= 6)
mp[x][y] = !mp[x][y];//以(sx,sy)为中心的十字花位置亮暗变换
}
}
int main()
{
for(int i = 1; i <= 5; ++i)
for(int j = 1; j <= 6; ++j)
cin >> ori_mp[i][j];
for(int k = 0; k < 32; ++k)//枚举5位二进制数字,作为第一列的情况
{
memcpy(mp, ori_mp, sizeof(ori_mp));//将mp设为初始灯的状态
for(int i = 1; i <= 5; ++i)//根据第1列点灯的情况进行点灯
{
if(lt[i][1] == 1)
light(i, 1);
}
for(int j = 2; j <= 6; ++j)
for(int i = 1; i <= 5; ++i)
{
if(mp[i][j-1] == 1)//如果(i,j-1)灯亮了,在第j-1列不能操作的情况下,只能在第(i,j)位置点灯,让(i,j-1)的灯灭掉。
{
lt[i][j] = 1;
light(i, j);
}
else//如果(i,j-1)的灯是灭的,在(i,j)处不能点灯
lt[i][j] = 0;
}
bool isDim = true;//第6列是否都灭了
for(int i = 1; i <= 5; ++i)
{
if(mp[i][6] == 1)
isDim = false;
}
if(isDim)//如果都灭了,得到了要求的点灯的结果
{
for(int i = 1; i <= 5; ++i)
{
for(int j = 1; j <= 6; ++j)
cout << lt[i][j] << ' ';
cout << endl;
}
return 0;
}
int i = 1;
lt[i][1]++;//把lt第1列看做一个高精度二级制数字,该操作使lt第1列这个二进制数字加1
while(lt[i][1] == 2)
{
lt[i][1] = 0;
i++;
lt[i][1]++;
}
}
return 0;
}