题目大意:
乔治有一些同样长度的小木棍,他把这些木棍随意砍成几段,直到每段的长度都不超过50。现在,他想把这些木棍拼接成原来的样子,但是却忘记了自己最开始有多少根木棍和他们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
解题思路:
DFS+剪枝。显然原始木棍的长度一定是n段小木棍长度之和sum的约数,只需要从小到大一次枚举sum的每一个约数m(m要大于n个数中最大的那一个,不然肯定不能切割成这n段小木棍),判断m是否可能为初始木棍长度就OK了。判断的方式使用DFS,主要的剪枝大致有如下几个,欢迎大神们继续补充。
1.木棍的长度从大到小排序,因为经验告诉我们越长的木棍灵活性越差,所以优先放置长一点的木棍。
2.如果当前长度的木棍无法和后面的木棍组成合法的解,那么和它同样长度的木棍一样不能,因此我们可以跳过和它长度相同的木棍。
3. 如果组成原始木棍的第一根木棍无法得出合法解,那么就直接返回不必搜索了,因为以后也不可能得出合法解。
4.(参照LRJ黑书) 如果某长度的木棍刚好能填满一根原始木棍并且剩余木棍不能组成合法解,那么我们考虑两种策略,一是使用更长的木棍,这显然是不可以的,而如果采用更短的小木棍,即使存在某个 也能填满原始木棍的方法 也不可能比用 长木棍更有希望获得合法解。因此当出现这种情况时我们可以直接退出当前的枚举,没有必要考虑其他木棍了,这应该是最重要的一个剪枝了,可惜不是自己想出来的,惭愧惭愧。
AC 0ms 660K
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> using namespace std; const int maxn = 100; int s[maxn],vis[maxn]; int ori,sum; int n; bool cmp(int a,int b) { return a>b; } int dfs(int p,int edge,int last) { if(last==ori) return 1; for(int i=p;i<=n;i++) { if(!vis[i]&&s[i]<=edge) { vis[i]=1; if(s[i]==edge) { if(dfs(1,ori,last-s[i])) return 1; } else if(dfs(i+1,edge-s[i],last-s[i])) return 1; vis[i]=0; if(s[i]==edge) return 0;//剪枝4 if(last==sum) return 0; while(s[i+1]==s[i]) i++;//跳过长度相同的 木棍 } } return 0; } int main() { while(scanf("%d",&n)!=EOF) { if(n==0) break; memset(s,0,sizeof(s)); memset(vis,0,sizeof(vis)); sum=0; for(int i=1;i<=n;i++) { scanf("%d",&s[i]); sum+=s[i]; } sort(s+1,s+1+n,cmp); ori=s[1]; while(sum%ori!=0) ori++; while(dfs(1,ori,sum)==0) { ori++; while(sum%ori!=0) ori++; } printf("%d\n",ori); } return 0; }