试题连接:
先想到的是枚举,可是30个开关如果直接枚举的话,2^(30)肯定超时,那么看是否能找到一个局部, 当这个局部被确定后,剩余其他部分状态只能是确定的一种。
经过观察会发现,第1行就是这样一个局部,即第1行开关一旦确定,其余几行开关状态也会确定。(因为第一行开关作用以后,还会有几盏灯未熄灭,这时就需要下一行对应位置的开关按下,来熄灭上一行未熄灭的灯,如此往复,直到最后一行,如果最后一行灯正好全部熄灭,则该方案可行,否则不可行。这里用位运算,可以更加简便。每一行只有6个灯,且状态都是用 0,1表示,而char 有8个bite,所以一个char 类型就可以表示一个灯的状态,(将6个灯的状态存在char类型的6个bite里面)。用长度为5的char型数组就可以装下灯的状态。
第一行6个灯,共有2^(6)=64种开关状态,而从 0~2^(6)-1 这64个数中的每一数的二进制 都可以表示开关的一个0 1组合 即从 00000000~11111111, --------------------------------------------------------------------------------------------------- 关于位运算可以先复习一下:
基本的位操作符有与、或、异或、取反、左移、右移这6种,它们的运算规则如下所示:
符号 | 描述 | 运算规则 by MoreWindows |
& | 与 | 两个位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) |
特别强调:0^其他,不变;1^其他,逆置
#include#include using namespace std;char orlight[5]; //灯原始状态char light[5]; //对灯的操作都在此数组进行char result[5];int GetBit(char c,int i) //读取灯的状态{ return (c>>i)&1;}void SetBit(char &c,int i,int v) //设置灯的状态{ if(v) c|=(1< >T; for(int t=0; t >s; SetBit(orlight[i],j,s); //设置第i行,第j列的灯的状态 } } for(int n=0; n<64; n++) { int switchs=n; //switchs 是0~63的整数,用其二进制数 表示灯的情况!!! //将 orlight数组拷贝到 light数组中,对灯的操作全在light中进行 memcpy(light,orlight,sizeof(orlight)); for(int i=0; i<5; i++) { result[i]=switchs; for(int j=0; j<6; j++) { if(GetBit(switchs,j)) { FlipBit(light[i],j); if(j>0) //如果开关左边有灯,则将其左边翻转 FlipBit(light[i],j-1); if(j<5) //如果开关右边有灯,则将其左边翻转 FlipBit(light[i],j+1); } } if(i<4) light[i+1]^=switchs; //下一行会受到上一行开关的影响 switchs=light[i]; //更新下一行的开关状态,其状态取决于上一行那些灯亮了 } if(light[4]==0) {//如果最后一行灯全熄灭,则输出 OutputResult(t+1,result); } } } return 0;}