POJ 1011 Sticks

地址:http://poj.org/problem?id=1011

问题描述:

乔治拿来一组等长的木棒,将它们随机地裁断,使得每一节木棒的长度都不超过50个长度单位。然后他又想把这些木棒恢复到为裁截前的状态,但忘记了木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棒的长度都用大于零的整数表示
 
输入:由多个案例组成,每个案例包括两行。第一行是一个不超过64的整数,表示裁截之后共有多少节木棒。第二行是经过裁截后,所得到的各节木棒的长度。在最后一个案例之后,是零。
 
输出:为每个案例,分别输出木棒的可能最小长度。每个案例占一行。
 

思路1:

题意: 给你n 木棍碎片,要求还原原木棍,且原木棍等长,问最短长度是多少。

分析: 各种剪枝

 

 1 /*1:越长的木棍对后面木棍的约束力越大,因此要把小木棍排序,
 2     按木棍长度从大到小搜索,这样就能在尽可能靠近根的地方
 3     剪枝。
 4 :当出现加上某根木棍恰好能填满一根原始木棍,但由在后面的
 5     搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。
 6 :考虑每根原始木棍的第一根木棍,如果当前枚举的木棍长度无
 7     法得出合法解,就不必考虑下一根木棍了,当前木棍一定是作
 8     为某根原始木棍的第一根木棍的,现在不行,以后也不可能得
 9     出合法解。也就是说每根原始木棍的第一根小木棍一定要成
10     功,否则就返回。
11 :剩下一个通用的剪枝就是跳过重复长度的木棍,当前木棍跟
12     它后面木棍的无法得出合法解,后面跟它一样长度的木棍也
13      不可能得到合法解,因为后面相同长度木棍能做到的,前面这
14      根木棍也能做到。
15  */
16 #include<stdio.h>
17 #include<string.h>
18 #include<stdlib.h>
19 int cmp(const void*p1,const void*p2)
20 {
21     return *(int*)p2-*(int*)p1;
22 }
23 int a[100],v[100];
24 int n,len,s,tot,min;
25 bool dfs(int k,int mi,int left)
26 {
27     int i;
28     if(left==min) 
29         return 1;
30     for(i=k;i<=n;i++)
31         if(!v[i]&&a[i]<=mi)
32         {
33             v[i]=1;
34             if(a[i]==mi&&dfs(1,len,left-a[i]))
35                 return 1;
36             else if(dfs(i+1,mi-a[i],left-a[i]))
37                 return 1;
38             v[i]=0;
39             if(a[i]==min)return 0;
40             if(left==tot)return 0;
41             if(mi==len)  return 0;
42             while(a[i+1]==a[i])
43                 i++;
44         }
45     return 0;
46 }
47 int main()
48 {
49     int i,res;
50     while(scanf("%d",&n),n)
51     {
52         tot=0;
53         for(i=1;i<=n;i++){
54             scanf("%d",&a[i]);
55             tot+=a[i];
56         }
57         qsort(a+1,n,sizeof(a[0]),cmp);
58         len=a[1];
59         res=len;
60         memset(v,0,sizeof(v));
61         res=tot;
62         for(;len<tot;len++)
63             if(tot%len==0&&dfs(1,len,tot))
64             {
65                 res=len;
66                 break;
67             }
68         printf("%d\n",res);
69     }
70     return 0;
71 }

 

 

思路2:

               思想很简单,一个接一个的把木棍拼起来,最后把木棍用光。
*             关键的地方是几个剪枝技巧:
*                   设所有木棍的总长度为 Sum, 最终的答案(长度)是 L。 
*             1. 首先要明白, Sum一定要能被 L 整除。 
*             2. L 一定 大于等于 题目给出的最长的木棍的长度 Max。
*                  由上述两点,我们想到,可以从 Max 开始递增地枚举 L, 
*                直到成功地拼出 Sum/L 支长度为 L 的木棍。
*                    搜索种的剪枝技巧: 
*             3. 将输入的输入从大到小排序,这么做是因为一支长度为 K 
*                的完整木棍,总比几支短的小木棍拼成的要好。
*                形象一些:
*                  如果我要拼 2 支长为8的木棍,第一支木棍我拼成 
*                          5 + 3
*                  然后拼第二支木棍但是失败了,而我手中还有长为 2 和 1 
*                  的木棍,我可以用 5 + 2 + 1 拼好第一支,再尝试拼第二
*                  支,仔细想一想,就会发现这样做没意义,注定要失败的。     
*                  我们应该留下 2+1 因为 2+1 比 3 更灵活。 
*             4. 相同长度的木棍不要搜索多次, 比如:
*                我手中有一些木棍, 其中有 2 根长为 4 的木棍, 当前搜索
*                状态是 5+4+.... (即表示长度为 5,4,2 的三支拼在一起, 
*                ...表示深层的即将搜索的部分), 进行深搜后不成功,故我
*                没必要用另一个 4 在进行 5+4+...
*             5. 将开始搜索一支长为 L 的木棍时,我们总是以当前最长的未
*                被使用的 木棍开始,如果搜索不成功,那么以比它短的开始
*                那么也一定不能取得全局的成功。因为每一支题目给出的木棍
*                都要被用到。
*                如果,有 
*                    4
*                    5 4 4 3 2
*                  想拼成长为 6 的木棍,那么从 5 开始, 但是显然没有能与 5
*                  一起拼成 6 的,那么我就没必要去尝试从 4 开始的,因为
*                  最终 5 一定会被遗弃。在拼第 2 3 ... 支木棍时,一样。 
*             6. 最后的最简单的一个就是,
*                      for(int i = 0; i < n; i++)
*                          for(int j = 0; j < n; j++)
*                               {}
*                与
*                      for(int i = 0; i < n; i++)
*                          for(int j = i+1; j < n; j++)
*                               {} 
*                的区别,这个不多说了。
*             7. 我用过的另一个剪枝,但是对 poj 的数据效果一般,
*                用一个数组, Sum[i] 保存 第 i 个木棍之后,即比第 i 枝
*                木棍短或与之相等所有的木棍的长度之和。
*                试想,如果剩余的所有木棍加在一起都不能和我当前的状态拼
*                出一直长为 L 的木棍(从长度来看),还有必要搜下去么? 

代码如下:

 1 #include <iostream>
 2 #include <string.h>
 3 #include <stdio.h>
 4 #include <algorithm>
 5 
 6 using namespace std;
 7 int sticks[65],n,sum,num,l;//l最小的与木棒的长度 num:圆木棒的个数  sum:圆木棒的总长度
 8 bool mark[65];
 9 bool cmp(int a,int b)
10 {
11     return a>b;
12 }
13 
14 //s:已经组成的小木棒的个数,le:当前搜索时正在组成的小木条的长度。pos:要组合的小木条的下标位置
15 bool dfs(int s,int le,int pos)
16 {
17     int i;
18     bool sign = (le == 0?true:false);
19     if(s==num)return true;
20     for(i = pos + 1;i < n;i++)
21     {
22         if(mark[i])continue;//如果这个小木棒组合过则从下一个开始
23         if(le + sticks[i]==l)//如果组合刚好组成l长度的木棒那么就要组合下一根木棒了即第s+1根
24         {
25             mark[i] = true;
26             if(dfs(s+1,0,-1))//第s根已经组合成功了组合下一根
27             return true;
28             mark[i] = false;
29             return false;//如果组合失败了那么就要返回false 而且这个木棒的状态应还原为没组合过
30         }
31         else if(le + sticks[i]<l)
32         //如果组合当前这根后长度仍然小于l那么从i开始往下找继续组合第S根木棒
33         {
34             mark[i] = true;
35             if(dfs(s,le+sticks[i],i))//如果可以组成就返回true
36             return true;
37             mark[i] = false;
38             if(sign)return false;
39             while(sticks[i]==sticks[i+1])i++;
40         }
41     }
42     return false;
43 }
44 
45 int main()
46 {
47     while(scanf("%d",&n)!=EOF,n)
48     {
49         sum = 0;
50 
51         for(int i = 0; i < n; i++)
52         {
53             scanf("%d",&sticks[i]);
54             sum += sticks[i];
55         }
56         sort(sticks,sticks+n,cmp);//将木棒从大到小排序
57         for(l = sticks[0]; l <= sum; l++)//从最大的开始搜索
58         {
59             if(sum%l==0)//如果该长度可以被整除那么可能是结果,否则继续
60             {
61                 num = sum/l;//num:记录该情况下可以组成的木棒的数目。
62                 memset(mark,false,sizeof(mark));//每种情况都要进行初始化,把所有的木棒的使用状态设为false
63                 if(dfs(1,0,-1))//当返回true时说明搜索成功了,可以组成该长度木条
64                 {
65                     printf("%d\n",l);
66                     break;
67                 }
68             }
69         }
70     }
71     return 0;
72 }

 

思路3:

1:初始状态:有N节木棒
最终状态:这N节木棒恰好被拼接成若干根等长的木棒
从初始状态到最终状态最多有多少条不同的“路径”?
Sum /maxParts。其中Sum是全部N节木棒的长度之和,maxParts是最长一节木棒的长度
每条“路径”对应一个木棒的长度。从木棒长度最小的那条可能“路径”开始,如果成功地的找到了这条“路径”,就解决了问题
 
2:构造一条木棒长度为L的“路径”:拼接木棒
在未被拼接的木棒中,找出一节最长的,开始拼接
从未拼接的木棒中,选择一个长度合适的木棒,使得拼接后的木棒长度≤L
找到了
在前面的拼接过程中曾试图用过相同长度的一节其他木棒,但发现这样拼接不成功,继续寻找能够进行拼接的木棒
把找到的那节木棒拼接上去。继续进行拼接
继续拼接成功,找到了“路径”
继续拼接不成功,把刚拼接的那节木棒拿下来,继续找下一个合适的未拼接木帮
没有找到:拼接失败
 
3:在已拼接部分未用长度为L1的木棒,则表明用长度为L1的木棒来拼接时是不成功的
下次拼接时也不能选择长度为L1的木棒,而应该选择长度为L2或L3的木棒
如果长度为L2或L3的木棒也不能选择,则需要替换已拼接部分的最后一节木棒
 
代码如下:
 1 #include <iostream.h>
 2 #include <memory.h>
 3 #include <stdlib.h>
 4 int T, S;
 5 int L;
 6 int anLength[65];
 7 int anUsed[65];
 8 int i,j,k;
 9 int Dfs(int nUnusedSticks, int nLeft);
10 int MyCompare( const void * e1, const void * e2) {
11     int * p1, * p2;
12     p1 = (int * ) e1;
13     p2 = (int * ) e2;
14     return * p2 - * p1;
15 }
16 main()
17 {
18     while(1) {
19         cin >> S;
20         if( S == 0 )
21             break;
22         int nTotalLen = 0;
23         for( int i = 0; i < S; i ++ ) {
24             cin >> anLength[i];
25             nTotalLen += anLength[i];
26         }
27         qsort(anLength,S,sizeof(int),MyCompare);
28 for( L = anLength[0]; L <= nTotalLen / 2; L ++ ) {
29         if( nTotalLen % L)
30             continue;
31         memset( anUsed, 0,sizeof(anUsed));
32         if( Dfs( S,L)) {
33             cout << L << endl;
34             break;
35         }
36      }
37     if( L > nTotalLen / 2 )
38         cout << nTotalLen << endl;
39     } // while
40 }
41 int Dfs( int nUnusedSticks, int nLeft)
42 // nLeft表示当前正在拼的棍子和 L 比还缺的长度
43 {
44     if( nUnusedSticks == 0 && nLeft == 0 )
45         return true;
46     if( nLeft == 0 ) //一根刚刚拼完
47         nLeft = L;  //开始拼新的一根
48     for( int i = 0;i < S;i ++) {
49         if( !anUsed[i] && anLength[i] <= nLeft) {
50             if( i > 0 ) {
51                 if( anUsed[i-1] == false 
52                    && anLength[i] == anLength[i-1])
53                     continue; //剪枝3
54             }
55             anUsed[i] = 1;
56 if ( Dfs( nUnusedSticks - 1,
57                  nLeft - anLength[i]))
58                 return true;
59             else {
60                 anUsed[i] = 0;//说明不能用i作为
61                         //第1条,
62                         //那么要拆以前的
63                         //棍子,i还可能用
64                         //在以前的棍子里,
65                          //因此要 anUsed[i] = 0;
66                 if( anLength[i] == nLeft || nLeft == L)
67                     return false;//剪枝2、1
68             }
69         }
70     }
71     return false;
72 }

思路4:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int sticks[100],n;
 4 bool used[100];
 5 int cmp(const void *x,const void *y)
 6 {
 7     return *(int *)y - *(int *)x;
 8 }
 9 bool find(int left,int num,int len)
10 {
11     int i;
12     if(left == 0 && num == 0)
13     return 1;
14     if(left == 0)
15     left = len;
16     for(i = 0;i < n;i ++)
17     {
18         if(sticks[i] <= left && !used[i])
19         {
20             used[i] = 1;
21             if(find(left - sticks[i],num-1,len))
22             return 1;
23             used[i] = 0;
24             if(sticks[i] == left || left == len)
25             return 0;
26         }
27     }
28     return 0;
29 }
30  
31 int main()
32 {
33     int i,sum = 0;
34     while(scanf("%d",&n) != EOF && n)
35     {
36         sum = 0;
37         for(i = 0;i < n;i ++)
38         {
39             scanf("%d",&sticks[i]);
40             sum += sticks[i];
41             used[i] = 0;
42         }
43         qsort(sticks,n,sizeof(int),cmp);
44         for(i = sticks[0];i <= sum;i ++)
45         {
46             if((sum % i == 0) && find(i,n,i))
47             {
48                 printf("%d\n",i);
49                 break;
50             }
51         }
52     }
53     return 0;
54 }

 

 

 
 
 
 
 
 

posted on 2012-08-14 17:43  mycapple  阅读(6050)  评论(2编辑  收藏  举报

导航