poj2965 递归枚举,直接枚举都超时了,最后求助于Discuss中的方法

本文针对POJ2965问题探讨了多种解法,包括递归、循环枚举及一种巧妙的数学思路,并实现了相应代码。通过数学分析简化算法,找到了最优解。

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 }
View Code

 

  后来考虑到递归函数较慢,所以改成使用循环直接枚举每一种情况,还是超时,代码如下:

 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 }
poj2965 直接枚举

 

  求大神指教该怎么优化。

再然后,看到一种大神的思路:

  对于一个“+”,对它所在的行列的每一个位置做题目中的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 }
poj2965 巧妙思路

感触:ACM离不开数学,数学的分析能大大精简算法,或是发现解题的方便方法。

转载于:https://www.cnblogs.com/basement-boy/p/3575584.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值