题意:N个木棒,问能拼成长度相等的几个木棒的最小长度。
分析:dfs+剪枝。(lrj黑书经典搜索)
直接贴别人的剪枝分析吧:
1.把所有木棍的长度从大到小排列,组合木棒时优先使用长的木棍,这样可以加快组合速度,并且对后面的剪枝有帮助。
2.木棒的长度一定是大于等于最长木棍的长度并且小于等于所有木棍长度的和,这个很容易证明。
3.木棒的长度一定是所有木棍长度的和的约数,这个也很容易证明。
4.在某一个木棒的组合过程中,对于当前的木棍stick[i],如果stick[i-1]没有被组合并且stick[i] == stick[i-1],那么不用考虑stick[i],显然stick[i]最终也不会被组合。
5.如果此次是在尝试第i个木棒的第一段,假设stick[j]为当前可以被使用的最长的木棍,如果此次组合失败,直接退出搜索,即退回到对第i-1个木棒的搜索。试想:失败说明现在使用stick[j]是不可行的,那么以后无论什么时候使用stick[j]都是不可行的,因为以后再处理stick[j]时可使用的木棍一定是当前可使用的木棍的子集,在更多木棍选择的情况下都不能组合成功,那么,在更少木棍选择的情况下一定不能组合成功。
Code:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=70;
int stick[maxn];
bool flag[maxn];
bool mark;
int n,sum;
bool cmp(int a,int b){
return a>b;
}
void dfs(int curl,int len,int res){
if(res==0) {mark=true; return ;}
for(int i=1;i<=n;i++){
if(mark) return ;
if(!flag[i]&&(curl-stick[i]>=0)){
flag[i]=true;
if(curl==stick[i]) dfs(len,len,res-stick[i]);
else dfs(curl-stick[i],len,res-stick[i]);
flag[i]=false;
if(curl==stick[i]) return ; //剩下的不能组成长度为len的若干个stick
if(curl==len) return ; //stick[i+1]不能和其他剩下的stick组合成长度为len的stick
while(stick[i]==stick[i+1]) i++; //长度和stick[i]相等的stick不必在搜下去
}
}
}
int main()
{
while(scanf("%d",&n),n){
sum=0;
for(int i=1;i<=n;i++){
scanf("%d",&stick[i]);
sum+=stick[i];
}
sort(stick+1,stick+n+1,cmp);
int ans=sum; mark=false; stick[n+1]=stick[n];
for(int i=stick[1];i<=sum/2;i++){
if(sum%i) continue; //这个剪枝很重要
memset(flag,false,sizeof(flag));
dfs(i,i,sum);
if(mark){ans=i; break ;}
}
printf("%d\n",ans);
}
return 0;
}