Description
小张是一个密室逃脱爱好者,在密室逃脱的游戏中,你需要解开一系列谜题最终拿到出门的密码。现在小张需要打开一个藏有线索的箱子,但箱子上有下图所示的密码锁。
每个点是一个按钮,每个按钮里面有一个小灯。如上图,红色代表灯亮,白色代表灯灭。每当按下按钮,此按钮的灯以及其上下左右四个方向按钮的灯状态会改变(如果原来灯亮则灯灭,如果原来灯灭则灯亮)。如果小张通过按按钮将灯全部熄灭则能可以打开箱子。
对于这个密码锁,我们可以先按下左上角的按钮,密码锁状态变为下图。
再按下右下角的按钮,密码锁状态变为下图。
最后按下中间的按钮,灯全部熄灭。
现在小张给你一些密码锁的状态,请你告诉他最少按几次按钮能够把灯全部熄灭。
Input
第一行两个整数 n,m(1 \leq n,m \leq 16 ) 。
接下来 n 行,每行一个长度为 m 的01字符串,0表示灯初始状态灭,1表示灯初始状态亮。
Output
一行一个整数,表示最少按几次按钮可以把灯全部熄灭。
Notes
第一个样例见题目描述,第二个样例按左上和右下两个按钮。
测试用例保证一定有解。
测试用例 1
3 3↵
100↵
010↵
001↵
以文本方式显示
3↵
1秒 64M 0
测试用例 2
2 3↵
111↵
111↵
以文本方式显示
2↵
1秒 64M 0
思路
本题使用的方法是深度优先搜索(DFS)。这是一种遍历的方法,先一条路走到黑,然后再回头找其他路。当然这道题不需要把所有情况暴力搜索一遍。我们只需要枚举第一层的2m种情况,第一层确认后事实上就完全确定了。接下来每一行都得把上一行还亮着的灯灭了,直到最后一行。它没有再下一行来灭灯了,所以在操作最后一行后它本身也是灭的。记得回溯,按一下,继续搜索;恢复,再次搜索。
代码
#include<stdio.h>
#include<string.h>
#define N 17
int n,m,min=N*N; //min表示最少步骤
int list[N][N],copy[N][N]; //list为输入,copy为操作第一行后的复制
void change(int *);
void push(int a[][N],int,int);
void dfs(int,int);
int count();
main()
{
int i,j;
scanf("%d %d%*c",&n,&m);
for(i=0;i<n;i++){
for(j=0;j<m;j++){
scanf("%1d",&list[i][j]);
}
}
dfs(0,0);
printf("%d\n",min);
}
/*改变单个灯状态*/
void change(int *p)
{
if(*p) *p=0;
else *p=1;
//*p=~(*p);
}
/*按下按钮后的变化*/
void push(int a[][N],int i,int j)
{
change(&a[i][j]);
if(i-1>=0) change(&a[i-1][j]);
if(i+1<n) change(&a[i+1][j]);
if(j-1>=0) change(&a[i][j-1]);
if(j-1<m) change(&a[i][j+1]);
}
/*DFS,枚举第一行的操作。当前操作针对list[0][j],sum表示此前对第一行按钮次数*/
void dfs(int j,int sum)
{
/*第一行操作完成*/
if(j==m){
int x=count(); //剩余步骤
if(x==-1) return; //无法完成
if(x+sum<min) min=x+sum; //更新最小值
return;
}
/*第一行共2^m种可能*/
push(list,0,j); //按
dfs(j+1,sum+1);
push(list,0,j); //还原,相当于不按
dfs(j+1,sum);
}
/*计数余下操作次数*/
int count()
{
int i,j,step=0;
/*复制第一行操作后的状态*/
for(i=0;i<n;i++){
for(j=0;j<m;j++){
copy[i][j]=list[i][j];
}
}
/*根据(i,j)状态操作(i+1,j)*/
for(i=0;i<n-1;i++){
for(j=0;j<m;j++){
if(copy[i][j]){
push(copy,i+1,j);
step++;
if(step>min) return -1;
}
}
}
/*检查倒数第二行操作完后最后一行的状态*/
for(j=0;j<m;j++){
if(copy[n-1][j]){
return -1;
}
}
return step;
}