#include <iostream>
#include <algorithm>
using namespace std;
//total能组成的木棒的组数,l:组成的木棒的长度
int total,l;
//num:输入的整数,sum:总长度
int num,sum;
typedef struct
{
int length;//木棒的长度
bool mark;//木棒是否被使用过
}Sticks;
Sticks sticks[70];
bool cmp(Sticks a,Sticks b)
{
return a.length>b.length;
}
//s 已组成的木棒数目,len已经组成的长度,pos搜索的木棒的下标的位置
int dfs(int s,int len,int pos)
{
if(s==total)return 1;
for(int i=pos+1;i<num;i++)
{
//如果这个木棒已经用过,则继续下一根
if(sticks[i].mark)continue;
if(len+sticks[i].length == l)
{
sticks[i].mark = true;
if(dfs(s+1,0,-1))return true;
sticks[i].mark = false;
return false;
}else if(sticks[i].length+len<l){
sticks[i].mark = true;
if(dfs(s,len+sticks[i].length,i))
return true;
sticks[i].mark = false;
//剪枝:如果当前搜索时,前边的长度为0,而第一根没有成功的使用,
//说明第一根始终要被废弃,所以这种组合必定不会成功
//此处的剪枝必须有,因为这里的剪枝会节省很多的无用搜索,
//我试了一下,此处剪枝省去就会超时的。。。。
if(len==0)return false;
//剪枝:如果当前和上一次搜到的木棒是一样长的则没必要再搜一次了
while(sticks[i].length==sticks[i+1].length)i++;
}
}
return false;
}
int main()
{
while(cin>>num&&num!=0)
{
sum = 0;//标记为0
for(int i = 0; i < num; i++)
{
cin>>sticks[i].length;
sum += sticks[i].length;
sticks[i].mark = false;
}
//将木棒按照长度从长到短的顺序排序
sort(sticks,sticks+num,cmp);
//从木棒的最长的那根开始搜索,因为最小的组合也会大于等于最长的那根
for(l = sticks[0].length; l <= sum; l++)
{
//剪枝一:如果不能被整除说明不能组成整数根木棒,搜下一个
if(sum%l!=0)continue;
total = sum / l;//得到木棒总数目
if(dfs(1,0,-1))
{
cout<<l<<endl;
break;
}
}
}
return 0;
}
经典搜索题,果然是到处充斥着剪枝才能过啊,我的代码离剪到极限还差很多
题目给出一大堆小棍子的长度,需要把他们拼成几根长度相等的大棍子,求大棍子的最短长度
#include <iostream>
#include <algorithm>
using namespace std;
int sticks[65];
int used[65];
int n,len;
bool dfs(int i,int l,int t)//i为当前试取的棍子序号,l为要拼成一根完整的棍子还需要的长度,t初值为所有棍子总长度
{
if(l==0)
{
t-=len;
if(t==0)return true;
for(i=0;used[i];++i); //剪枝1:搜索下一根大棍子的时候,找到第一个还没有使用的小棍子开始
used[i]=1; //由于排序过,找到的第一根肯定最长,也肯定要使用,所以从下一根开始搜索
if(dfs(i+1,len-sticks[i],t))return true;
used[i]=0;
t+=len;
}
else
{
for(int j=i;j<n;++j)
{
if(j>0&&(sticks[j]==sticks[j-1]&&!used[j-1])) //剪枝2:前后两根长度相等时,如果前面那根没被使用,也就是由前面那根
continue; //开始搜索不到正确结果,那么再从这根开始也肯定搜索不出正确结果,此剪枝威力较大
if(!used[j]&&l>=sticks[j]) //剪枝3:最简单的剪枝,要拼成一根大棍子还需要的长度L>=当前小棍子长度,才能选用
{
l-=sticks[j];
used[j]=1;
if(dfs(j,l,t))return true;
l+=sticks[j];
used[j]=0;
if(sticks[j]==l) //剪枝4:威力巨大的剪枝,程序要运行到此处说明往下的搜索失败,若本次的小棍长度刚好填满剩下长度,但是后
break; //面的搜索失败,则应该返回上一层
}
}
}
return false;
}
bool cmp(const int a, const int b)
{
return a>b;
}
int main()
{
while(cin>>n&&n)
{
int sum=0;
for(int i=0;i<n;++i)
{
cin>>sticks[i];
sum+=sticks[i];
used[i]=0;
}
sort(sticks,sticks+n,cmp); //剪枝5:从大到小排序后可大大减少递归次数
bool flag=false;
for(len=sticks[0];len<=sum/2;++len) //剪枝6:大棍长度一定是所有小棍长度之和的因数,且最小因数应该不小于小棍中最长的长度
{
if(sum%len==0)
{
if(dfs(0,len,sum))
{
flag=true;
cout<<len<<endl;
break;
}
}
}
if(!flag)
cout<<sum<<endl;
}
return 0;
}
dfs+剪枝~剪枝是关键~
主要的剪枝有:
1.从大到小排序,相同的长度如果已验证过不行就直接跳过;
2.范围从木棍最大长度maxx到总长tot,maxx和tot在输入的时候就记录下.