算法练习---熄灯问题

题目

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。

输入

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。

输出

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

Sample

InputOutput
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0

代码

//关灯问题 
#include<stdio.h>
int judge(int x,int y);//判断是否出界 
int press(int x,int y,int chess[][5]);//进行一次按压操作 
void print(int chess[][5]);//打印  
void fresh(int chess[][5],int anochess[][5]);//更新为备份 
void change(int x,int y, int chess[][5]);
void up(int x,int chess[][5]){
	if (chess[x][0]==0){
		chess[x][0]=1;
	}else if(chess[x][0]==1){
		chess[x][0]=0;
		up(x+1,chess);
	}
}


int main(){
	int chess[6][5]={0};//用于操作 
	int anochess[6][5]={0};//用于恢复 
	int key[6][5]={0};//用于记录 
	
	
	for (int y=0;y<5;y++){
		for (int x=0;x<6;x++){
			scanf("%d",&anochess[x][y]) ;
			chess[x][y]=anochess[x][y];
		}
	}//读取棋盘 
	int s=0;
	for(int time=0;time<64;time++){
//		printf("1---------------\n");
//		print(chess);
		//下面遍历第一行
		if(key[0][0]==0&&s==0){
			s=1;
		}else{
			up(0,key);
		}
//		printf("1------\n");
//		print(key);
//		printf("1------\n");//这里调试用
		
		//下面把预定的按完
		for(int x=0;x<6;x++){
			if(key[x][0]==1){
				press(x,0,chess);
			}
		} 
		
//		printf("1------\n");
//		print(chess);
//		printf("1------\n");
		
		
		//下面开始遍历
		for(int y=0;y<4;y++){
			for (int x=0;x<6;x++){
				if(chess[x][y]==1){
					press(x,y+1,chess);
					key[x][y+1]=1;
				}
			}
		} 
//		printf("2--------\n");
//		print(chess);
//		printf("2---------------\n");
//		print(chess);
		//下面检查最后一行 
		int jud=0;
		for (int x=0;x<6;x++){
			if(chess[x][4]==1){
				jud=1;
				fresh(chess,anochess);
				for (int p=1;p<5;p++){
					for (int q=0;q<6;q++){
						key[q][p]=0;
					}
				}
//				printf("2------\n");
//				print(key);
//				printf("2------\n");
				break;
			} 
		}
		if (jud==0){
			print(key);
		}
		
	} 
	return 0;
	
}




int judge(int x,int y){
	if ((x>=0&&x<=5)&&(y>=0&&y<=4)){
		return 1;
	}
	return 0;
}


int press(int x,int y,int chess[][5]) {
	//本体 
	if (chess[x][y]==0){
		chess[x][y]=1;
	}else{
		chess[x][y]=0;
	}
	//右边 
	if (chess[x+1][y]==0&&judge(x+1,y)){
		chess[x+1][y]=1;
	}else if(chess[x+1][y]==1&&judge(x+1,y)){
		chess[x+1][y]=0;
	}
	//左边 
	if (chess[x-1][y]==0&&judge(x-1,y)){
		chess[x-1][y]=1;
	}else if(chess[x-1][y]==1&&judge(x-1,y)){
		chess[x-1][y]=0;
	}
	//上边 
	if (chess[x][y+1]==0&&judge(x,y+1)){
		chess[x][y+1]=1;
	}else if(chess[x][y+1]==1&&judge(x,y+1)){
		chess[x][y+1]=0;
	}
	//下边 
	if (chess[x][y-1]==0&&judge(x,y-1)){
		chess[x][y-1]=1;
	}else if(chess[x][y-1]==1&&judge(x,y-1)){
		chess[x][y-1]=0;
	}
	return 0;
}

void print(int chess[][5]){
	for (int y=0;y<5;y++){
		for (int x=0;x<6;x++){
			if(x!=5){
			printf("%d ",chess[x][y]);
			}else{
				printf("%d",chess[x][y]);
			}
		}
		printf("\n");
	}
}

void fresh(int chess[][5],int anochess[][5]){
	for (int y=0;y<5;y++){
		for (int x=0;x<6;x++){
			chess[x][y]=anochess[x][y];
		}
	}
}

void change(int x,int y, int chess[][5]){
	if(chess[x][y]==0){
		chess[x][y]=1;
		
	}else{
		chess[x][y]=0;
	}

}//改变状态 

思路

很自然地联想到使用二维数组来表示按钮矩阵。关键是灭灯的思路。

根据上面的规则,我们知道(1)第2次按下同一个按钮时,将抵消第1次按下时所产生的结果。因此,每个按钮最多只需要按下一次;(2)各个按钮被按下的顺序对最终的结果没有影响;(3)对第1行中每盏点亮的灯,按下第2行对应的按钮,就可以熄灭第1行的全部灯。如此重复下去,可以熄灭第1、2、3、4行的全部灯。同样,按下第1、2、3、4、5列的按钮,可以熄灭前5列的灯。

//题外话:笔者曾做过2023的八省联考数学16题,一样是关灯问题,如果当时有电脑就好了罢hhh

当没有更好的算法时,就利用计算机的长项----暴力破解!

根据上面的讨论,一旦第一行确定,我们可以对2345行进行遍历找到解,即对于2345行的按压方式我们不关心,唯一的问题在于第一行的按压方法。一共有2^6=64种情况。由于笔者是初次接触编程,一开始用了很多if。。。在灵光一现之后,笔者想到了递归的方法

void up(int x,int chess[][5]){
	if (chess[x][0]==0){
		chess[x][0]=1;
	}else if(chess[x][0]==1){
		chess[x][0]=0;
		up(x+1,chess);
	}
}

 这个函数通过递归,可以实现模拟二进制数的+1操作(遍历第一行时,可将第一行看作六位二进制数)

以下便再无难点,唯一要注重的是边界条件的判断。

另一些题外话

这是笔者的第一篇博客,笔者目前大一,正在学习ctf-re方向和算法基础,如果有人看到了这里,非常感谢你的耐心!欢迎您和笔者我私信交流,一同学习进步。

同时也记录一下笔者现在的心情,这道题笔者花了很久才成功ac,ac的一瞬间,笔者感受到强烈的快乐与满足感。编程之路漫漫,笔者很开心能够拥有这样的学习心态,也希望看到文章的你们,能够像笔者一样潜心编程,热爱编程!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值