费解的开关
你玩过“拉灯”游戏吗?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的棋盘来观察一下状态,灰色表示没亮的部分,棋盘的每一个位置都有按一下和不按两种选择,暴力枚举当然可以是解题方法,但是2^25数量级肯定过不了时间限制
再来看这个棋盘,我们可以枚举第一行的32种操作,当然每个位置只操作一次,如果操作两次的话相当于没有操作纯属浪费步数,那么比如操作了第二位和第五位之后,棋盘变成了
此时第一行已经是操作之后的了,不可以在对第一行进行操作了,那么想得到最终全亮的结果,必须操作第二行让第一行全亮,即第一行暗的地方的下面必须操作一次,操作后结果为:
同理操作第3,4,5行,可以看出,第一行的操作确定了之后,那么后面的四行的操作就也唯一确定,如果在操作完了第5行之后,第5行不是全亮状态,那么就说明该棋盘状态是无解的,代码实现如下:
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; const int N = 6; char g[N][N], backup[N][N]; int dx[5] = {-1, 0, 0, 1, 0}, dy[5] = {0, 1, -1, 0, 0}; void turn(int x, int y) { for(int i = 0 ; i < 5 ; i++) { int a = x + dx[i], b = y + dy[i]; if(a < 0 || a > 4 || b < 0 || b > 4) continue; //判断是否超出了范围 g[a][b] ^= 1; } } int main() { int T; cin >> T; while(T --) { for(int i = 0 ; i < 5 ; i ++) cin >> g[i]; int res = 10; for(int op = 0 ; op < 32 ; op ++ ) //第一行的五格,一共有32中操作方法,全部尝试一遍 { memcpy(backup, g, sizeof g); //把棋盘拷贝走,每一次turn操作都是在原来的棋盘上进行操作 int step = 0; for(int i = 0 ; i < 5 ; i++ ) { if(op >> i & 1) { step ++; turn(0, i); } } for(int i = 0 ; i < 4 ; i++ ) //第一行定下之后,后面的四行操作被唯一确定 for(int j = 0 ; j < 5 ; j++) if(g[i][j] == '0') { step ++; turn(i + 1, j); } bool flag = false; for(int i = 0 ; i < 5 ; i++) //判断一下最后一行是不是全亮 if(g[4][i] == '0') { flag = true; break; } if(!flag) res = min(res, step); memcpy(g, backup, sizeof g); //第一行的32种操作其中的一种执行完了一次,然后恢复一下棋盘 } if(res > 6) res = -1; cout << res << endl; } return 0; }
代码中的turn函数有一个g[a][b] ^= 1的操作,因为操作的是字符,让字符的'0' 和'1'相互转换,'0'的ASCll是48,'1'的ASCll是49 他俩只有最后一位不同,异或一个1正好可以完成相互转换的操作
代码中还用到了位运算来记录这32次操作,从0到32每一个都对应这一个二进制数,二进制数从0到31也经历了这5位从全0到圈1的过程,所以哪一位是1就对哪一位进行操作也正好枚举了全部的情况,(op >> i & 1)就是判断了op的第i位是不是1