题意:
一个5*6的开关矩阵,拨动每个开关,都会使得它本身以及前后左右四个开关反转。现在给出所有开关的初始状态,问使得所有开关处于关状态,需要拨动的开关。
解法:
首先看到这题就应该想到方程组。或者说,是一个模2方程组。
但是,这个会给我们带来求解的麻烦。求解方程组有经典的高斯消元法,但是出现模运算,确实让人头疼。这里,我们想到了异或运算,联系高斯消元的本质,是用一个方程来代换另外一个方程,十进制模二运算中的加、减运算与二进制中的异或运算正好对应!然后下面的事情就简单多了
有一个小技巧,使用位运算能够大大简化编程复杂度。
可以将原来矩阵的每一行压缩成一个32位整数,这样每次消元的过程中选择列主元的过程可以用排序轻松解决~,然后消去的过程和回代的过程也就非常好实现了。用这种方法,这题的代码量可以控制在60行以内。
忽然想起了老队长说过的话:100行以内的程序才是正解,做了这么多题,越来越发现这句话是多么的经典。话说现在郭老大在马化腾那应该算个红人了~
还有件很囧的事,使用STL里的greater仿函数竟然要包括一个叫functional的头文件,甚是诡异。。代码:
1 # include <cstdio>
2 # include <algorithm>
3 # include <functional>
4 # include <cstring>
5 using namespace std;
6 inline void setbit(int &num,int bit)
7 {
8 num|=1<<(30-bit);
9 }
10 inline bool getbit(int &num,int bit)
11 {
12 if(num&(1<<(30-bit))) return true;
13 else return false;
14 }
15 int main()
16 {
17 int test;
18 scanf("%d",&test);
19 for(int t=1;t<=test;t++)
20 {
21 int e[30];
22 memset(e,0,sizeof(e));
23 for(int i=0;i<30;i++)
24 {
25 int tmp;
26 scanf("%d",&tmp);
27 setbit(e[i],i);
28 if(i%6!=0) setbit(e[i],i-1);
29 if(i%6!=5) setbit(e[i],i+1);
30 if(i/6!=0) setbit(e[i],i-6);
31 if(i/6!=4) setbit(e[i],i+6);
32 if(tmp) setbit(e[i],30);
33 }
34 for(int i=0;i<30;i++)//消元
35 {
36 sort(e+i,e+30,greater<int>());
37 if(getbit(e[i],i))
38 for(int j=i+1;j<30;j++)
39 if(getbit(e[j],i))
40 e[j]^=e[i];
41
42 }
43 for(int i=29;i>=0;i--)//回代
44 if(getbit(e[i],i))
45 for(int j=i-1;j>=0;j--)
46 if(getbit(e[j],i))
47 e[j]^=e[i];
48 printf("PUZZLE #%d\n",t);
49 for(int i=0;i<30;i++)
50 {
51 if(e[i])
52 printf("%d",getbit(e[i],30));
53 else printf("0");
54 if(i%6==5) printf("\n");
55 else printf(" ");
56 }
57 }
58 return 0;
59 }