题目:Sticks
题意:起初有一些相等的木棍,然后被人砍成了n个木条。长度分别给出,现在需将n个木条组合拼成回原来的木棍,问原始木棍的最小可能长度?
思路:虽然知道用dfs,但没想到怎么实现多个相等的判断,然后参考了后做的,其实还是那些套路,就是变通不了。。。不过这个剪枝比较NB。。。
(1)枚举:枚举原始木棍长度,从总长度/i i时n~1
(2)搜索:枚举给出的数组,枚举位置不是每次都从0开始,而成在递归中的pos参数,每次是从上一个的下一位开始的,递归中累加上木棍长度,当长度符合枚举的原始木棍长度时,再继续搜索,但是参数需要改变,pos从0开始,sum木棍长度也从0开始,当个数继续+1,当个数与n相等时所有所有木条都组合拼成木棍了。
(3)剪枝:
①递减排序,可以递归层数;
②枚举原木棍长度应是总长度的倍数且大于最达的木条长度,其他的没有必要搜索,因为根本达不到!
③当搜索时第i个木条时,第i个木条还未选(即放在还原标记数组的后面)时,如果后面的长度和i的相等,就没有必要再枚举,直接i++;(有点懵)
④当第i个木条没有找到时,没有必要再继续寻找了,直接return剪枝。(还是有点懵!)
注意:以后判断标记数组时用if(visit[i]) continue; 不能和有条件的判断中加 !visit[i] 这样时间会很慢!
参考:JeraKrs博客
代码:
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int maxn = 100 + 5; bool cmp(int a,int b){return a > b;} int n,stick[maxn],visit[maxn]; int ans,len; bool dfs(int steps,int pos,int sum){ if(steps == n) return true;//当到达第n个,说明所以木条都组合拼成了 for(int i = pos;i < n;i++){ if(visit[i]) continue;//关键,访问过的跳过 if(sum + stick[i] < ans){ visit[i] = 1; if(dfs(steps+1,i+1,sum + stick[i])) return true; visit[i] = 0; while(stick[i] == stick[i+1] && i+1 < n) i++;//剪枝:当i个数和第i+i数相等时,不需要再枚举,因为依然小于ans } else if(sum + stick[i] == ans){ visit[i] = 1; if(dfs(steps+1,0,0)) return true;//当达到组合值时,将组合木条长度和下标位置归为0,继续搜索,因为是几个相等的木条,所以不是找到一个就行。。。 visit[i] = 0; return false; } if(sum == 0) return false;//剪枝:当上面都没有return时,程序会走到这一步,说明第i个木条没有找到合适的组合,直接return } return false; } inline int solve(){ for(int i=n;i>=0;i--){//从大到小分 if(len % i == 0 && len / i >= stick[0]){//总长度的倍数且平分长度大于初始的最长木条才可进行组合 memset(visit,0,sizeof(visit)); ans = len / i;//当前组合长度 if(dfs(0,0,0)) return ans; } } return -1; } int main() { while(scanf("%d",&n)!=EOF && n){ len = 0; for(int i=0;i<n;i++){ scanf("%d",&stick[i]); len += stick[i]; } sort(stick,stick+n,cmp);//递减排序 printf("%d\n",solve()); } return 0; }