AC95. 费解的开关
题目描述:
你玩过“拉灯”游戏吗?25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。下面这种状态
10111
01101
10111
10000
11011
在改变了最左上角的灯的状态后将变成:
01111
11101
10111
10000
11011
再改变它正中间的灯后状态将变成:
01111
11001
11001
10100
11011
给定一些游戏的初始状态,编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。
输入格式
第一行输入正整数n,代表数据中共有n个待解决的游戏初始状态。
以下若干行数据分为n组,每组数据有5行,每行5个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。
输出格式
一共输出n行数据,每行有一个小于等于6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。
对于某一个游戏初始状态,若6步以内无法使所有灯变亮,则输出“-1”。
数据范围
0<n≤500
输入样例:
3
00111
01011
10001
11010
11100
11101
11101
11110
11111
11111
01111
11111
11111
11111
11111
输出样例:
3
2
-1
去年写过一篇很类似的题目的题解,半年多过去了,果然都快忘干净了,当时就觉得那个题目很怪,这次遇到这样一道“变种”题就无从下手了。
首先还是一样的,先分析题目。这道题本质是给我们一个规定为5*5的01矩阵,并给定一个操作可以把某个位置及其相邻位置(即上下左右)的0或1都变为相反的数,即1变成0,0变成1,然后问我们能否在六步内把这个矩阵变为全1矩阵。鉴于数据不是很大,我们考虑考虑枚举。
先来想想,由题我们可以得出什么性质?做过类似的题的同学肯定能想到:操作的顺序不会影响最终的结果,且每个位置至多按一次(按两次无意义,毕竟这道题所有点就两种状态,按一次是另一种,再按一次不就又回去了)。还有一条最重要的也是很难想到的性质:若固定了某一行,则满足题意的答案最多就只有一种。为什么?咱们这么来想,假设我们把第一行给固定了,那么是不是接下来就只能通过第二行去修改第一行的值了?而若是要达到目标状态,第二行是不是就必须把第一行的0全都变成1?这样一来第二行也就只有一种操作方案,就是如上的能把第一行都变成1的操作方案。这样往下推,每一行都是如此。换言之,只要我们固定了第一行,那么对每种不同的第一行的初始方案,就只有一条路可以走了。这也就是我们的枚举思路:枚举第一行的所有操作对应的情况,然后向下递推,最后判断即可。
还有一点需要说明,就是关于第一行的枚举,我们可以采用位运算,更方便。具体思路是:从00000(也就是0)到11111(也就是十进制的31),我们把0当作不做操作,1当作进行操作,那么从0到31,就应当包括所有的32种操作方案。(比如10101,就代表对第一行第1,3,5列进行操作的情况),我们可以写出如下代码:
for (int i = 0;i < 5;i++)
{
if (k >> i & 1)
{
b[1][5 - i] ^= 1;
b[2][5 - i] ^= 1;
b[1][5 - i - 1] ^= 1;
b[1][5 - i + 1] ^= 1;
step++;
}
}
其中k就是我们遍历0到31的所用的变量,也就是0到31其中一个数,k>>i&1的操作作用是取出k的二进制数从右往左数第i+1个数并判断它是不是1。如果是的话,就继续操作,下面的异或解释一下:0和1异或为1,相同的(如0和0)异或则为0。那么0或1与它异或1出来的数就一定是相反的。
最后我们可以写出如下代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
int n;cin >> n;
while (n--)
{
bool a[7][7];
int ans = 0x7fffffff;
for (int i = 1;i <= 5;i++)
{
for (int j = 1;j <= 5;j++)
{
char x;cin >> x;
a[i][j] = x - '0';
}
}
for (int k = 0;k < 32;k++)
{
int step = 0;
bool b[7][7];
memcpy(b, a, sizeof(a));
for (int i = 0;i < 5;i++)
{
if (k >> i & 1)
{
b[1][5 - i] ^= 1;
b[2][5 - i] ^= 1;
b[1][5 - i - 1] ^= 1;
b[1][5 - i + 1] ^= 1;
step++;
}
}
for (int i = 2;i <= 5;i++)
{
for (int j = 1;j <= 5;j++)
{
if (!b[i - 1][j])
{
b[i][j] ^= 1;
b[i - 1][j] ^= 1;
b[i + 1][j] ^= 1;
b[i][j - 1] ^= 1;
b[i][j + 1] ^= 1;
step++;
}
}
}
for (int i = 1;i <= 5;i++)
{
for (int j = 1;j <= 5;j++)
{
if (!b[i][j])
{
step = -1;
break;
}
}
}
if (step != -1)
ans = min(step, ans);
}
if (ans <= 6)
cout << ans << endl;
else cout << -1 << endl;
}
return 0;
}