题目描述
你玩过“拉灯”游戏吗?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 X 5的0–1矩阵,通过六次以内的变换使得状态矩阵中的元素全变为1。
每次变化会改变此位置及其上下左右的位置的所有元素的值。
可以只枚举第一行的所有可以改变的状态,当第一行确定后,不对第一行进行操作,则第一行为0的位置想要变成1只能由第二行来改变,改变完成后第一行全部为1,第二行的状态也随之确定, 如此进行下去。
当循环结束后,若最后一行全部为1说明可以整个矩阵全为1,否则就是不合法状态。
C++代码实现
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 6, M = 5, INF = 7;
int g[N][N], back[N][N];//存放灯的状态
int dx[4] = {0, 1, 0, -1}, dy[4] = {-1, 0, 1, 0};
void change(int x, int y) {
//改变x,y及其上下左右的灯
g[x][y] = !g[x][y];
for(int i = 0; i < 4; ++i) {
int r = x+dx[i], c = y+dy[i];
if(r>=0 && c>=0 && r<M && c<M)
g[r][c] = !g[r][c];
}
}
int main() {
int n;
scanf("%d", &n);
//n次输入
while(n--) {
char row[N];
for(int i = 0; i < M; ++i) {
scanf("%s", row);
for(int j = 0; j < M; ++j)
back[i][j] = row[j]-'0';
}
int ans = INF;
//枚举第一行的所有可能的改变
for(int i = 0; i < 1<<M; ++i) {
memcpy(g, back, sizeof g);
int cnt = 0;
//改变第一行
for(int j = 0; j < M; ++j) {
if(i>>j&1) change(0, j), cnt++;
}
//第一行确定后其余行的改变随之确定,第一行灭的灯需要在第二行改变打开,同理第二行决定第三行。。。
for(int j = 1; j < M; ++j) {
for(int k = 0; k < M; ++k) {
if(!g[j-1][k]) change(j, k), cnt++;
}
if(cnt > 6) break;
}
//判断最后一行是否合法
for(int j = 0; j < M; ++j) {
if(!g[M-1][j]) {
cnt = INF;
break;
}
}
ans = min(ans, cnt);
}
if(ans <= 6) printf("%d\n", ans);
else puts("-1");
}
return 0;
}