POJ 1011 Sticks
地址:http://poj.org/problem?id=1011
问题描述:
思路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 #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 }