费解的开关(二进制+递推+思维)
-
题意:5*5的灯阵,每次按一盏灯的开关,并且这盏灯的上下左右也受到相同的影响(0->1,1->0),求使给定灯阵全1的最少步数。
-
题解:
- 每盏灯最多点击一次,点击两次相当于没有点击。
- 最重要的性质:如果我们确定了第1行的灯的情况的话,那么后面的行数都可以依此递推,当前行灭的灯只能由下一行同一列的灯使之点亮。
举个例子
11011
10110
01111
11111
第一行中第三盏灯为0,那么必须通过第二行的第3张灯将其点亮,当前行的灯只能由下一行的灯点亮,这样才不会影响当前行其他灯的情况。所以只需要确定第一行的灯的情况即可,第一行的灯控制第二行灯的点击情况,通过第二行灯将第一行灭的灯点亮后, 第二行灯处于灭状态的灯由第三行点亮,依次类推
- Code:
//模块化编程思想
#include <bits/stdc++.h>
using namespace std;
#define mem(a) memset((a),0,sizeof(a))
#define fo(i,a,b) for(int (i)=(a);(i)<(b);(i)++)
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)//宏定义,编译时展开,占用编译时间
#define sf(x) scanf("%d",&(x))
const int inf=(0x7f7f7f7f);
const int maxn=3000;
int n;
int mp[5][5];
int dx[]={0,-1,0,1,0};
int dy[]={0,0,1,0,-1};
void turn(int x,int y){
fo(i,0,5){
int xx=x+dx[i];
int yy=y+dy[i];
if(xx>=0&&xx<5&&yy>=0&&yy<5){
mp[xx][yy]^=1;
}
}
}
char mmp[5][5];
int work(){
int ans=(1<<30);
int cnt=0;
//fo(i)循环枚举第一行灯的所有可能的点击情况,10010(表示点击第一盏灯和第四盏灯)
fo(i,0,31){
//初始化,
fo(x,0,5)fo(y,0,5)mp[x][y]=mmp[x][y]-'0';
cnt=0;
//通过二进制数解码出灯的点击情况
fo(j,0,5)
if((i>>j)&1){
cnt++;
turn(0,j);
}
//依次处理前4行灯
fo(j,0,4){
fo(k,0,5){
if(mp[j][k]==0){
turn(j+1,k);//通过下一行的灯使其点亮
cnt++;
}
}
}
//检查第五行灯是否是全亮的情况,如果不是由于前4行是全1的情况
//第五行灭的灯无法点亮,故当前方案不合理
bool is_ok=1;
fo(j,0,5)if(mp[4][j]==0){
is_ok=0;
break;
}
if(is_ok)
ans=min(ans,cnt);
}
if(ans>6)return -1;
return ans;
}
int main(){
sf(n);
while(n--){
fo(i,0,5)scanf("%s",mmp[i]);
//fo(i,0,5)fo(j,0,5)cout<<mmp[i][j];
cout<<work()<<endl;
}
return 0;
}