问题:
有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上、下、左、右边的灯都会改变状态 (亮→暗,暗→亮))。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果,给定矩阵中每盏灯的初始状态,求一种按按钮方案,使得所有的灯都熄灭,t组输入,输出一个5×6只包含0,1(0代表不按,1代表按)的矩阵,每一个值对应该位置灯泡的情况。

初次遇到熄灯问题时我的第一感觉是用枚举(穷举),但每一盏灯都有0(不按)或1(按)两种可能 ,对于一个5×6的矩阵,总共就有2^30种可能,显然这会导致TLE,且实现起来十分复杂繁琐(虽然这个题目的数据范围很小)。因此便排除了这种方法,之后就是十分茫然,不知从何下手。后面通过他人的讲解,我恍然大悟,觉得其方法十分妙。在结和他人与自己的理解后,我写下这篇博客,希望能对和我当初一样迷茫的人有所帮助。
思考:
其实解决这个问题最重要的是分析题目,通过二进制去解决(如果你在看到这里就已经有了方案,请允许我叫你一声大佬,如果还是没有思路也没有关系请继续往下看)。首先,我们知道在解决复杂数学题时,常用的思想是由特殊到一般,从简到繁去逐步分析解剖问题 。同样,这种思想也可以应用在算法竞赛上。因此我们先分析第一行的情况,假定我们已经知道了第一行每一个按钮的取值情况 ,那么我们就可以确定第二行的按钮的取值情况(因为按一次按钮只能够影响到它的上下,左右四盏灯,因此当a[i][[j]=1时则a[i+1][[j]就必须为1,这样才能使a[i][j]为0)。再以此类推,直到最后一行。我们只需判断最后一行的灯是否全灭(即全为0)即可得出答案。也就是说我们只要枚举第一行的情况就可以推出其他行的情况,进而得出答案。接下来我们就只需要去思考如何用代码去实现就行了。而在进行代码实现时我们需要借助二进制去完成。利用二进制是解决该问题的重要手段,同时也是难点。我们将矩阵的每一行都看成一个二进制数,则每一行灯泡的取值情况都对应一个数,这样我们就可以将n×m的矩阵转换为一个包含n个字符的字符数组(每个字符代表一行灯泡的取值情况,该字符二进制的第i位表示该行第i盏灯的取值情况),这样在进行代码实现时就可以大大提升效率,减少了空间,代码也会变得简单

代码如下:

1. #include<bits/stdc++.h>
2. using namespace std;
3. #define sc(x) scanf("%d",&x)
4. #define pr(x) printf("%d",x)
5. int GetBit(char c,int i) { //得到字符c二进制的第j位;
6. return (c>>i)&1;
7. }
8. void SetBit(char &c,int i,int x) { //将字符c二进制的第i位置为x(0或1);
9. if(x) c |= ( 1<<i );    //和1进行或运算可将数置为1;
10. else c &=~( 1<<i );   //和0进行与运算可将数置为0;
11. }   
12. 13. 
13. void ReBit(char &c,int i) { //将字符c二进制的第i位取反;
14. c^=(1<<i);              //对c进行异或运算
15. }
16. void Output(char c[]) { //将字符数组以二进制的形式输出;
17. cout<<"答案如下"<<endl;
18. for(int i=0; i<5; i++) {
19. for(int j=0; j<6; j++) {
20. pr(GetBit(c[i],j));
21. if(j<5) cout<<" ";
22. }
23. cout<<endl;
24. }
25. }
26. int main() {
27. int t;
28. char light1[6];
29. char light2[6];
30. char ans[6];
31. sc(t);
32. while(t--) {
33. for(int i=0; i<5; i++) {
34. for(int j=0; j<6; j++) {
35. int x;
36. sc(x);          //输入数据
37. SetBit(light1[i],j,x);
38. }
39. }
40. for(int i=0; i<64; i++) {
41. int n=i;                 //穷举第一行的情况,根据第一行得出其它行的情况;
42. memcpy(light2,light1,sizeof(light1));   //将原始字符数组拷贝到c中(这样可以方便对数组进行处理且不会改变原数组的值);
43. for(int j=0; j<5; j++) {
44. ans[j]=n;               //将答案储存起来;
45. for(int k=0; k<6; k++) {
46. if(GetBit(n,k))  {    //如果第k位为1,则要对周围灯泡进行取反运算
47. ReBit(light2[j],k);
48. if(k>0) ReBit(light2[j],k-1);
49. if(k<5) ReBit(light2[j],k+1);
50. }
51. }
52. if(j<4) light2[j+1] ^= n;  /*对j+1行进行操作,这步算法十分巧妙,降低了运行时间,要仔细思考*/
53. n=light2[j];       /* 此句话是重点,通过上一行的灯泡情况确定下一行灯泡的操作情况*/
54. }
55. if(light2[4]==0) {    //结束循环的条件,若light2[4]==0则说明所有灯泡均已熄灭
56. Output(ans);
57. break;
58. }
59. }
60. }
61. return 0;
62. }
63. 64. 
64. ```