枚举 - bailian 2811:熄灯问题

题目描述

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

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

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。根据上面的规则,我们知道
1)第2次按下同一个按钮时,将抵消第1次按下时所产生的结果。因此,每个按钮最多只需要按下一次;
2)各个按钮被按下的顺序对最终的结果没有影响;
3)对第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
来源
1222

解题分析

如果对所有5 * 6个按钮枚举,一共有2^30中策略,枚举空间太大,可以找到枚举空间的一个状态局部,在这道题中第一行就是一个状态局部,对第一行的开关状态进行枚举,一共有64种开关状态,在第一行按下之后,第一行的灯有些被熄灭了,有些还在亮着,对于那些亮着的等,再在第二行的对应列按下,将第一行的所有灯熄灭,然后第二行还有一些灯亮着,再在第三行进行相同操作,直到按下第5行的灯,这个时候,如果第五行的灯全部熄灭,那么所有灯就灭了,我们就找到了一个按开关的策略。
从这道题可以看出,在枚举空间较大的时候,可以通过找到一个状态局部,对状态局部进行枚举,进而其他状态就确定下来,或者大幅度减少,这样,枚举空间就可以减少很多。
在这道题的实现过程中,使用char数组分别保存灯的状态,按钮按下策略,然后每个比特表示灯亮或者灭,按钮按下或者不按下。在i行按钮按下后,i+1行的灯的状态可以用按位异或i行的按钮策略直接得到。

解题代码

#include <cstdio>
#include <cstring>

char oriLights[5];
char lights[5];
char result[5];

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 FlipBit(char & c, int i){//将c的第i个比特翻转
    c ^= 1 << i;
}

void OutputResult( char result[]){
    for(int i = 0; i < 5; i++){
        for(int j = 0; j < 6; j++){
            printf("%d", GetBit(result[i], j));
            if(j < 5) printf(" ");
        }
        printf("\n");
    }
}

int main(){
    for(int i = 0; i < 5; i++){
        for(int j = 0; j < 6; j++){
            int a;
            scanf("%d", &a);
            SetBit(oriLights[i], j, a);
        }
    }
    for(int i = 0; i < 64; i++){ //这是对第一行按下策略的尝试,不同的比特组合对应不同的整数,这个方法叫做状态压缩
        int switchs = i; //swiths 表示当前行的按下状态
        memcpy(lights, oriLights, sizeof(oriLights)); //将输入灯的状态复制到按下过程中灯的状态lights数组中
        for(int i = 0; i < 5; i++){
            result[i] = switchs;
            for(int j = 0; j < 6; j++){ //对第i行按下按钮后灯的状态进行改变
                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]; //更新i+1行按下策略
        }
        if(lights[4] == 0){
            OutputResult(result);
            break;
        }
    }
    return 0;
    
}

最后如果不懂,可以参考:https://www.bilibili.com/video/BV1Ht41187bC?t=30&p=5

posted @ 2020-04-14 17:47  zhangyue_lala  阅读(280)  评论(0编辑  收藏  举报