poj 1222 EXTENDED LIGHTS OUT - 熄灯问题-详解

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

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

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

样例输入:
2
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
0 0 1 0 1 0
1 0 1 0 1 1
0 0 1 0 1 1
1 0 1 1 0 0
0 1 0 1 0 0
样例输出:

PUZZLE #1

1 0 1 0 0 1

1 1 0 1 0 1

0 0 1 0 1 1

1 0 0 1 0 0

PUZZLE #2

1 0 0 1 1 1

1 1 0 0 0 0

0 0 0 1 0 0

1 1 0 1 0 1

1 0 1 1 0
解题思路:
因为每栈灯的最多只需按下一次,考虑到数据量比较小,一共只有 5 X 6 = 30 栈灯,可以用枚举的方法。但是枚举所有按钮的状态的次数为 2^30,明显复杂度太高了,需要考虑进行优化。
事实上,只用枚举第 1行或者第 1 列按钮的状态即可。下面以枚举第 1行按钮的状态为例。因为第 1 行灯的状态由 第 1 行和第 2 行的按钮决定,所以第 1 行按钮的状态确定之后,为使第一行的等全部熄灭,第 2 行的按钮的状态也就唯一确定了,例如,当第 1 行按钮的状态确定之后:
1、如果位置(1,j)上的灯是亮着的,则需按下位置(2,j)上的按钮;
2、如果位置(1,j)上的灯是熄灭的,则不能按位置(2,j)上的按钮;
因此,在第 1 行按钮的状态确定之后,第 2行按钮的状态也就确定了。同理,当第 1 行和 第 2行按钮的状态确定后,第3行按钮的状态也就确定了。因此,程序只需在枚举第 1 行的状态之后,再依次确定剩余的各行按钮的状态,并判断最后一行的灯是否全部熄灭即可。这样,需要,枚举的状态数就变成 2^6 = 64.
具体在求解每个按钮的状态时,根据一个灯被开关两次相当于抵消这一前提,可以用异或的方式实现;第 i 行第 j 列按钮的状态 ans( i , j ),由初始的(i-1,j)位置灯的初始状态 block(i-1,j)等周围的按钮的状态决定。即:
ans(i,j)=block(i-1,j)^ ans(i - 1 , j) ^ ans^(i - 1,j - 1) ^ ans(i-1,j+1) ^ ans(i-2,j)



#include<iostream>
#include<memory>
#include<cstring>
using namespace std;
char oriLights[10]; 
char lights[10];
char result[10];
int GetBit(char c,int i) {
    //取c的第i位
    return ( c >> i )    & 1;
}
void SetBit(char & c,int i, int v) {
    //设置c的第i位为v
    if( v )
    c |= ( 1 << i);
    else
    c &= ~( 1 << i);
}
void Flip(char & c, int i) {
    //将c的第i位为取反
    c ^= ( 1 << i);
}
void OutputResult(int i,char result[]){//输出结果 
    cout<<"PUZZLE #"<<i<<endl; 
    for(int i=0;i<5;i++){
        for(int j=0;j<6;j++){
            cout<<GetBit(result[i],j);
            if(j < 5)
                cout<<" "; 
        } 
        cout<<endl;
    } 
} 
int main(){
    int T;
    cin>>T;
    for(int t=1;t<=T;t++){
        for(int i=0;i<5;i++){
            for(int j=0;j<6;j++){
                int s;
                cin>>s;
                SetBit(oriLights[i],j,s); //读入最初灯状态
            }
            for(int n=0;n<64;n++){//遍历首行开关的64种状态
                int switchs = n;//第i行的开关状态
                memcpy(lights,oriLights,sizeof(oriLights)); 
                for(int i=0;i<5;i++){
                    result[i] = switchs;//第i行的开关方案
                    for(int j=0;j<6;j++){
                        if(GetBit(switchs,j)){
                            if(j > 0){
                                FlipBit(lights[i],j-1); //改左灯
                            } 
                            FlipBit[lights[i],j];//改开关位置的灯
                            if(j < 5)
                                FlipBit(lights[i],j+1); //改右灯

                        } 
                    } 
                    if(i < 4)
                        lights[i+1] ^= switchs;
                    switchs = lights[i]; 
                } 
                if(lights[4] == 0){
                    OutputResult(t,result);
                    break;
                } 
            } 
        } 
    } 
    return 0;
} 

有什么不懂可以在评论区问我,我会及时回答的,感谢阅读,希望能帮到您!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值