CH0201费解的开关——二进制枚举

目录:2020年8月12日12:28:42

CH0201 费解的开关

你玩过“拉灯”游戏吗?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”。

样例输入:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

样例输出:

3
2
-1

思路:
首先先明确两个问题:

1.对于一个开关,我们按下的次数以及按它周围的开关对它所造成的影响,其实就两种情况,改变状态或者不变。显然偶数次就是不变。所以对于每个开关,我们最多按下一次。

2.如果我们确定了第一行中有那几个开关按下,那第一行的状态也就确定了。对于第一行还关着的灯,我们只能通过它的下一行的同一列的开关来改变。这样当我们把第一行的灯全部变亮的时候,第二行的状态也就确定了。这样递推把前四行的灯全部变亮。对于最后一行,如果还有灯关着,那我们就不可能把所有灯打开。

第一行灯只有5个,那我可以用二进制枚举第一行的所有情况。

代码:

#include <iostream>
#include <cstring>
using namespace std;

int a[6];
int aa[6];
char s[6];
int ans;

void dj(int x, int y){
	aa[x] ^= (1 << y);
	if(x != 1) aa[x-1] ^= (1 << y);
	if(x != 5) aa[x+1] ^= (1 << y);
	if(y != 4) aa[x] ^= (1 << (y+1) ) ;
	if(y != 0) aa[x] ^= (1 << (y-1) ); 
}

void bp(int p){
	int k = 0;
	memcpy(aa, a, sizeof(a));
	
	for(int i = 0; i < 5; i++){
		if(!((p >> i) & 1)){
			dj(1,i);
			if(++k >= ans ) return ;
		}
	}
	
	for(int x = 1; x <= 4; x++){
		for(int y = 0; y <= 4; y++){
			if(!((aa[x] >> y) & 1)){
				dj(x+1,y);
				if(++k >= ans) return ;
			}
		}
	}
	if(aa[5] == 31) ans = k;
}

void fun(){
	memset(a, 0, sizeof(a));
	ans = 7;
	for(int i = 1; i < 6; i++){
		cin>>(s+1);
		for(int j = 1; j < 6; j++ ){
			a[i] = (a[i] << 1) + (s[j] - '0');
		}
	}
	for(int p = 0; p < (1 << 5); p++) bp(p);
	
	if(ans == 7) cout<<"-1"<<endl;
	else cout<<ans<<endl;
} 

int main(){
	int n;
	cin>>n;
	while(n--) fun();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44923545/article/details/107947188
今日推荐