95.费解的开关

AcWing 95. 费解的开关

你玩过“拉灯”游戏吗?

25盏灯排成一个 5×5的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 1表示一盏开着的灯,用数字 0表示关着的灯。

下面这种状态

1
2
3
4
5
10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

1
2
3
4
5
01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

1
2
3
4
5
01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6步以内使所有的灯都变亮。

输入格式

第一行输入正整数 n,代表数据中共有 n个待解决的游戏初始状态。

以下若干行数据分为 n组,每组数据有 5 行,每行 5个字符。

每组数据描述了一个游戏的初始状态。

各组数据间用一个空行分隔。

输出格式

一共输出 n行数据,每行有一个小于等于 6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若 6步以内无法使所有灯变亮,则输出 −1。

数据范围

0<n≤500

输入样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

1
2
3
3
2
-1

题解:

  1. 首先我们要想到更改第i行的某一个灯(不改变同一行其它灯的状态)是改变这个灯上(下)的灯。
  2. 由此我们可以定下从上到下的方向来一行一行枚举灯的亮灭情况(灭就点亮)。(递推由来)
  3. 由于我们从上向下枚举,而这种枚举只改变第2-5行的灯,无法判断第一行是否有所更改会导致更少的步数,所以需要枚举第一行所有情况(32钟)。

ps:因为3中的原因,为了简化枚举所有情况的代码[doge],使用了位运算的操作。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<bits/stdc++.h>
using namespace std;
const int N=10;
char a[N][N],b[N][N];
int dx[5]={0,-1,0,1,0},dy[5]={0,0,-1,0,1};

void turn(int x,int y)
{
for(int i=0;i<5;i++)
{
int tx=x+dx[i],ty=y+dy[i];
if(tx<0 && tx>=5 && ty<0 && ty>=5)continue;
a[tx][ty]^=1;//0->1,1->0的高级操作。
}
}

int main()
{
int t;cin>>t;
while(t--)
{
for(int i=0;i<5;i++)cin>>a[i];
memcpy(b,a,sizeof a);
int res=10;
for(int op=0;op<32;op++)
{
int sum=0;
for(int i=0;i<5;i++)if(op>>i&1)turn(0,i),sum++;//如果这个数第i位是1,turn(0,i)
for(int i=0;i<4;i++)
{
for(int j=0;j<5;j++)
{
if(a[i][j]=='0')turn(i+1,j),sum++;
if(sum>res)break;
}
if(sum>res)break;
}
int fl=1;
for(int i=0;i<5;i++)
{
if(a[4][i]=='0')fl=0;
}
if(fl==1)res=min(res,sum);
memcpy(a,b,sizeof b);
}
if(res<=6)cout<<res<<endl;
else cout<<-1<<endl;
}
}