熄灯问题(枚举)

该博客探讨了一种矩阵灯泡问题的解决方案,其中每次按下按钮会影响其周围灯泡的状态。通过分析,发现只需枚举第一行开关状态即可确定所有灯泡最终状态,通过异或运算确定每一行的开关状态,最终检查是否能熄灭所有灯。代码实现中,使用异或操作计算开关状态,并验证第五行是否全灭,以找到正确答案。
摘要由CSDN通过智能技术生成

题目来源:http://bailian.openjudge.cn/practice/2811/

Description:

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。
在这里插入图片描述
用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

在这里插入图片描述

需要按下哪些按钮,恰好使得所有的灯都熄灭。

根据上面的规则,我们知道

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

输入

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

输出

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

样例输入

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

Answer:

首先很容易想到的是,枚举 5 ∗ 6 = 30 5*6=30 56=30​​个开关的状态(按下[1]/不按[0]),然后判断不同的组合情况,能否使得全部的灯熄灭。这是一个暴力搜索,但是枚举的状态高达 2 30 2^{30} 230,时间复杂度很高。

其实,从上面的题目中加粗的部分可以知道,我们如果确定了第一行灯的状态(亮[1]/不亮[0]),那么第二行开关的状态就可以根据第一行灯的状态随之确定(如果第一行某个灯是亮着的,那么我们需要它熄灭,就得按下第二行相应的开关,反之亦然;第二行开关的所有操作是为了使第一行的所有灯灭),第二行的开关状态确定之后,那么第二行灯的状态就会随着确定。然后,第三行开关的状态就会根据第二行灯的状态随之确定(第三行开关的所有操作是为了使第二行的所有灯灭),第三行开关的状态确定之后,第三行灯的状态也就确定了。第四行的开关状态根据第三行灯的状态随之确定(第四行开关的所有操作是为了使第三行的所有灯灭),那么第四行灯的状态也就确定了。第五行的开关状态根据第四行的灯的状态随之确定(第五行开关的所有操作是为了使第四行的所有灯灭),那么第五行灯的状态也就确定了。到了现在,可以知道,前四行的灯已经灭了,只剩下第五行的灯,如果这个时候,第五行的灯正好也全灭,那么这一次的全部开关操作是一个可行的解,否则不是。

从上面的过程中,我们可以知道,我们只需要枚举第一行的开关的状态就可以了,然后第二行、第三行、第四行、第五行的开关状态都会随之确定,最后只需要判断第五行的灯的状态是否全灭,这个题就解出来了。只需要枚举 2 6 2^6 26次数就可以了。

假设灯的状态数组为block[][],开关的状态数组为press[][]。

那么press[i][j]受什么影响呢?

我们知道press[i][j]肯定是根据block[i-1][j]的状态来确定的,如果block[i-1][j]=1,表明灯是亮的,那么我们就需要按下press[i][j],让灯灭;反之,如果block[i-1][j]=0,那么press[i][j]=0。但是block[i-1][j]是受周围开关的影响的,因为周围开关变化,就会影响周围灯的亮灭。
p r e s s [ i − 2 ] [ j ] p r e s s [ i − 1 ] [ j − 1 ] p r e s s [ i − 1 ] [ j ]   ( b l o c k [ i − 1 ] [ j ] ) p r e s s [ i − 1 ] [ j + 1 ] \begin{array}{|c|c|c|} \hline &press[i-2][j]&\\ \hline press[i-1][j-1] & press[i-1][j] \, (block[i-1][j]) & press[i-1][j+1] \\ \hline && \\ \hline \end{array} press[i1][j1]press[i2][j]press[i1][j](block[i1][j])press[i1][j+1]
我们可以看出block[i-1][j]是受press[i-1][j]、press[i-1][j-1]、press[i-1][j+1]、press[i-2][j]以及block[i-1][j]本身灯的状态有关。

假设block[i-1][j]初始状态为0,也就是灭的状态,press[i-1][j]=1, press[i-1][j-1]=1, press[i-1][j+1]=1, press[i-2][j]=0,经过这一系列的操作之后,block[i-1][j]=1,灯是亮着的,那么press[i][j]=1,需要按下,使得block[i-1][j]熄灭。这个过程其实就是一个异或的过程,大家可以自己验证一下,那么: p r e s s [ i ] [ j ] = b l o c k [ i − 1 ] [ j ] press[i][j]=block[i-1][j] press[i][j]=block[i1][j] ^ p r e s s [ i − 1 ] [ j ] press[i-1][j] press[i1][j]​ ^ p r e s s [ i − 1 ] [ j − 1 ] press[i-1][j-1] press[i1][j1] ^ p r e s s [ i − 2 ] [ j ] press[i-2][j] press[i2][j] ^ p r e s s [ i − 1 ] [ j + 1 ] press[i-1][j+1] press[i1][j+1]

代码如下:

#include<iostream>
#include<bits/stdc++.h>
#include<bitset>
using namespace std;

int block[6][8];//多开1行 2列 防止越界
int press[6][8];

bool guess(){
    //从第二行开始改变 灯 开关的状态
    //计算开关的状态
    for(int i=2; i<=5; i++){
        for(int j=1; j<=6; j++){
            press[i][j]=block[i-1][j]^press[i-1][j]^press[i-1][j-1]^press[i-2][j]^press[i-1][j+1];
        }
    }
    //判断第五行的灯是否全部熄灭
    for(int j=1; j<=6; j++){
        int temp = block[5][j]^press[5][j]^press[4][j]^press[5][j-1]^press[5][j+1];
        if(temp == 1) return false;
    }
    //第五行全部熄灭
    return true;

}
void enumerate(){
    //000000~111111  63
    for(int i=0; i<64; i++){
        //将i转换成二进制
        int temp = i, count=6;
        while(temp!=0){
            press[1][count--] = temp%2;
            temp /= 2;
        }
        bool tmp = guess();
        if(tmp){
           for(int i=1; i<=5; i++){
                for(int j=1; j<=6; j++){
                    cout<<press[i][j]<<" ";
                }
                cout<<endl;
            }
        }
    }
}
int main(){
    //输入block 5*6
    for(int i=1; i<=5; i++){
        for(int j=1; j<=6; j++){
            cin>>block[i][j];
        }
    }
    //枚举第一行开关的状态
    enumerate();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值