题目
有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。
在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。
请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。
输入
5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。
输出
5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。
Sample
Input | Output |
---|---|
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的一瞬间,笔者感受到强烈的快乐与满足感。编程之路漫漫,笔者很开心能够拥有这样的学习心态,也希望看到文章的你们,能够像笔者一样潜心编程,热爱编程!