9.解密游戏

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北辰2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值