题目: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;
}