poj2965 递归枚举,直接枚举都超时了,最后求助于Discuss中的方法
2014-03-01
poj2965,题目类似 poj1753,一开始用 1753 的代码改的,自己跑好多组数据都正确,但是超时了。代码如下:
1 ///2014.3.1 2 ///poj2965 3 4 #include <cstdio> 5 6 int chess; ///二进制末16位表示锁盘,1表示锁着、0表示打开状态 7 int step; 8 bool flag = false; ///标记是否是已经找到解 9 int solution[16]; 10 ///方便最后输出拨动的位置 11 int forEasyCoutRow[16] = {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4}; 12 int forEasyCoutCol[16] = {1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4}; 13 14 void flip(int i) ///拨锁 15 { 16 for(int j=0 ; j<16 ; j++){ 17 if( j%4 == i%4 || j/4 == i/4 ) 18 chess = chess ^ 0x1<<j ; 19 } 20 } 21 22 void dfs(int i,int deep) ///deep表示搜索的深度,也是翻棋子的次数 23 { 24 if( step == deep ){ 25 flag = ( chess==0 ); /// 0 分别表示全部的锁都已打开 26 return; 27 } 28 if( i>15 || flag ) return; 29 flip(i); 30 solution[deep] = i; 31 dfs(i+1,deep+1); 32 if( !flag ){ 33 flip(i); 34 dfs(i+1,deep); 35 } 36 return; 37 } 38 39 void init() 40 { 41 chess = 0; 42 char temp; 43 for(int i=0 ; i<16 ; i++){ 44 scanf("%c",&temp); 45 if( temp!='+' && temp!='-' ) 46 scanf("%c",&temp); 47 if( temp == '+' ){ 48 chess = chess | 0x1 << i ; 49 } 50 } 51 } 52 53 int main( ) 54 { 55 // freopen("in","r",stdin); 56 // freopen("out","w",stdout); 57 58 init(); 59 60 for(step=0 ; step<=16 ; step++ ){ 61 dfs(0,0); 62 if( flag ) 63 break; 64 } 65 66 printf("%d\n",step); 67 for(int i=0 ; i<step ; i++ ){ 68 printf("%d %d\n",forEasyCoutRow[ solution[i] ],forEasyCoutCol[ solution[i] ]); 69 } 70 return 0; 71 }
后来考虑到递归函数较慢,所以改成使用循环直接枚举每一种情况,还是超时,代码如下:
1 ///2014.3.1 2 ///poj2965 3 4 #include <cstdio> 5 6 int chess; ///二进制末16位表示锁盘,1表示锁着、0表示打开状态 7 int chess2; 8 ///方便最后输出拨动的位置 9 int forEasyCoutRow[16] = {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4}; 10 int forEasyCoutCol[16] = {1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4}; 11 12 void flip(int i) ///拨锁 13 { 14 for(int j=0 ; j<16 ; j++){ 15 if( j%4 == i%4 || j/4 == i/4 ) 16 chess2 = chess2 ^ (0x1<<j) ; 17 } 18 } 19 20 void init() 21 { 22 chess = 0; 23 char temp; 24 for(int i=0 ; i<16 ; i++){ 25 scanf("%c",&temp); 26 if( temp!='+' && temp!='-' ) 27 scanf("%c",&temp); 28 if( temp == '+' ){ 29 chess = chess | 0x1 << i ; 30 } 31 } 32 } 33 34 int main( ) 35 { 36 // freopen("in","r",stdin); 37 // freopen("out","w",stdout); 38 39 init(); 40 int chess3 =0; 41 for( ; chess3<=65535 ; chess3++){ 42 chess2 = chess; 43 for(int i=0 ; i<16 ; i++){ 44 if( chess3 & 0x1<<i ) 45 flip(i); 46 } 47 if( chess2==0 ) 48 break; 49 } 50 51 int step=0; 52 for(int i=0 ; i<16 ; i++){ 53 if( chess3 & 0x1<<i ) 54 step++; 55 } 56 printf("%d\n",step); 57 for(int i=0 ; i<16 ; i++){ 58 if( chess3 & 0x1<<i ){ 59 printf("%d %d\n",forEasyCoutRow[i],forEasyCoutCol[i]); 60 } 61 } 62 return 0; 63 }
求大神指教该怎么优化。
再然后,看到一种大神的思路:
对于一个“+”,对它所在的行列的每一个位置做题目中的flip,结果是只有该“+”会变成“-”,其余位置没有变化。
所以对初始状态下所有“+”都做一次上述操作,最终的结果是一部分位置对比初始状态做了奇数次翻转,另一部分做了偶数次。偶数次等于没翻转,因此只要打印出奇数次的位置即可。
根据这个思路我的代码如下:
1 ///2014.3.1 2 ///poj2965 3 4 #include <cstdio> 5 6 int chess; ///二进制末16位表示锁盘,1表示锁着、0表示打开状态 7 int chess2[16]; 8 ///方便最后输出拨动的位置 9 int forEasyCoutRow[16] = {1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4}; 10 int forEasyCoutCol[16] = {1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4}; 11 12 void flip(int i) ///拨锁 13 { 14 for(int j=0 ; j<16 ; j++){ 15 if( j%4 == i%4 || j/4 == i/4 ) 16 chess = chess ^ (0x1<<j) ; 17 } 18 } 19 20 void init() 21 { 22 chess = 0; 23 char temp; 24 for(int i=0 ; i<16 ; i++){ 25 scanf("%c",&temp); 26 if( temp!='+' && temp!='-' ) 27 scanf("%c",&temp); 28 if( temp == '+' ){ 29 chess = chess | 0x1 << i ; 30 } 31 } 32 for(int i=0 ; i<16 ; i++){ 33 chess2[16] = 0; 34 } 35 } 36 37 int main( ) 38 { 39 // freopen("in","r",stdin); 40 // freopen("out","w",stdout); 41 42 init(); 43 for(int i=0 ; i<16 ; i++){ 44 if( chess & 0x1<<i ){ 45 for(int j=0 ; j<16 ; j++){ 46 if( j%4 == i%4 || j/4 == i/4 ){ 47 flip(j); 48 chess2[j]++; 49 } 50 } 51 } 52 } 53 54 int step=0; 55 for(int i=0 ; i<16 ; i++){ 56 if( chess2[i]%2 ) 57 step++; 58 } 59 printf("%d\n",step); 60 for(int i=0 ; i<16 ; i++){ 61 if( chess2[i]%2 ){ 62 printf("%d %d\n",forEasyCoutRow[i],forEasyCoutCol[i]); 63 } 64 } 65 return 0; 66 }
感触:ACM离不开数学,数学的分析能大大精简算法,或是发现解题的方便方法。