熄灯问题(枚举)

熄灯问题(poj 1222)——难

http://poj.org/problem?id=1222

 

 问题描述:

有一个由按钮组成的矩阵,其中每行有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

思路:首先每一个灯其实只有两种状态,要么开,要么关。而且没按两次这个灯又回复原样。第一想法是对于每一个灯枚举相应的状态,最后判断是否符合条件,但是一共有30个灯,那么就需要枚举2的三十次方的数,太大了,所以现在肯定要想办法把枚举的次数来减少。

              所以我们怎么减少复杂度呢?答案是用局部。如果局部被确定了那么剩余的所有情况也随之确定了或者是只有不是为数不多的n种,所以在枚举的过程种只要枚举局部就好了。在这个题目里,局部就是第一行,只要第一行确定了,第二行必须要熄灭的话只能“插缝”,因为如果不插空的话,会把第一行原来熄灭的灯又重新点亮了。总之在进行第一行的枚举的时候,第一行有一部分的灯是亮的,有一部分是暗的,为了熄灭第一行的亮的灯就只能用第二行的灯实现。后面的行就依次类推,到了最后一行的时候判断是不是全部都已经熄灭了。

              然后就是储存的问题了,一般来说的话是用二维数组来储存的,这题学到了一种方法就是用二进制来储存,因为矩阵只有0和1两个数组,用二维数组的话就要用30个int,如果我用二进制储存只要用一个char的一维数组就好了,因为一个空间有8个bit完全够用了。但是如果这样储存的话就要对位运算非常熟悉。

              还有一个点就是新学到的位运算的遍历,一共要遍历6个位,只要从1一直遍历到2的6次方减一就可以遍历完全部的可能性。如果不是这样做的话就要设计六重循环,每一重循环都是0和1.

 

代码:

 

 1 #include <memory>
 2 #include <string>
 3 #include <cstring>
 4 #include <iostream>
 5 using namespace std;
 6  char orilights[5];
 7  char lights[5];
 8  char result[5];
 9 int GetBit(char c,int i ){
10     //这个函数就是得到c的第i位的数字 
11     return (c >> i ) & 1 ;//c右移i再与1进行与运算
12     //1其实就是000001,其他都是0所以与运算就不会改变除了第一位以外的位
13      
14 } 
15 void SetBit(char &c, int i , int v){
16     //这个函数是用来设置某一位的值是0还是1的 
17     if(v){
18         c |= ( 1 << i );
19     }else{
20         c &= ~( i << i );
21         //这两个位运算只要熟悉了就好了 
22     }
23 } 
24 
25  void FlipBit (char c,int i){
26      //这个函数是用来将位反转
27      // 用异或运算
28      //与1异或不变,与0异或反转 
29      c ^= (1 << i );
30  }
31  
32  
33  void OutPutResult(int t,char result[]){
34      cout<<"Puzzle #"<<t<<endl;
35      //开关 矩阵 了
36      for(int i = 0;i<5;i++){
37          for(int j = 0;j<6;j++){P
38              cout<<GetBit(result[i],j);
39              if(j<5){
40                  cout<<" ";
41             }
42         }
43         cout<<endl;
44     } 
45 } 
46 int main(){
47     int T;
48     cin>>T;
49     for(int t = 1;t<T;t++){
50         for(int i = 0;i<5;i++){
51             for(int j = 0;j<6;j++){
52                 int s;
53                 cin>>s;
54                 SetBit(orilights[i],j,s);
55             }
56         }
//上面其实就是将每一位读入数组。设计每一个是1还是0
57         for(int n = 0;n<64;n++){
//这里就是上面有提到的位运算的枚举,值要到63就好了
58             int switchs = n;
59             memcpy(lights,orilights,sizeof(orilights));//这个函数是用来把orilights的二进制赋给lights的,这个就是再内存里面直接复制
60             //这个函数是直接cpy二进制的值 
61             for(int i = 0;i<5;++i){
62                 result[i] = switchs;
                   //每一次处理完一行都要把上一行的switchs保存到result中去
63                 for(int j = 0;j<6;j++){
64                     if(GetBit(switchs,j)){//得到这个数第j个位置的数
65                         if(j>0){//不是最左边的数 
66                             FlipBit(lights[i],j-1);
67                         }
68                         FlipBit(lights[i],j);
69                         if(j<5){//不是最右边的数
70                             FlipBit(lights[i],j+1);
71                         }
72                     }
73                     if(i<4){
                       //前面已经把当前行全部都处理好了,现在就是要处理下一行了
                       //这个还是很巧妙的,只要用一个异或就好了,只要第一行是1的话,就反转让第一行熄灯,如果是0的话就i保持原样
74                         lights[i+1] ^= switchs;
75                     }
76                     switchs = lights[i];
77                 }
78                 if(lights[4]==0){
79                     //全部灭了
80                     OutPutResult(t,result);
81                     break; 
82                 }
83             }
84         }
85     }
86     return 0;
87 }
88  

 

 

 

 

总结:首先这题如果要用位运算做完就要对位运算非常熟。还要会位运算的遍历,对枚举的优化还要非常熟。后面这题还要自己多看看。

 

posted @ 2022-01-28 20:00  prize  阅读(106)  评论(0编辑  收藏  举报