DFS搜索。
很好的剪枝范例。
由一堆长度(可能)不等的小木条,拼接出几组长度相等的新木条,要求这些长度相等的新木条的长度尽可能小。
显然,所有拼接方案中,新木条长度最小的方案,即取原木条中最长木条的长度为新木条的长度。当然,这种方案不一定可行,但是不会存在新木条长度比这种方案更小的可能。
同时,所有拼接方案中,新木条长度最大的方案,即是将所有原木条拼成一根新木条,这时新木条数量只有一根,长度是所有原木条长度之和。显然,不会存在新木条长度比这种方案还大的可能。这种拼法一定可行,但不一定是最优解。
所以,最优解的新木条长度,一定在最长的原木条的长度与所有原木条的长度的和之间。
还要注意的是,拼出的几根新木条长度相等,意味着任一可行方案中,所有原木条的长度之和,一定是新木条长度的整数倍。而这个倍数,即为这种方案中,新木条的数目。
我们在拼接的时候,采取一组一组拼的方式。这一组原木条拼出了一个目标长度,我们便再从剩下的原木条中继续尝试拼接。直到所有的原木条都拼接完毕,即拼接出的组数与预定方案的新木条数目相等为止。在拼接的过程中,我们采取从长到短的顺序搜索。取剩下的原木条中尽可能大的木条,与当前组的木条尝试拼接。显然这里有点贪心的味道。当然不一定就能一次拼好,要不断的通过递归进行调整。
关于这道题的剪枝,其实也有点贪心的味道。举个例子,现在一个完整的木条A,差一定长度可达到目标的新木条长度。如果所差的长度,既可以用一根较长的木条B直接补齐,也可以用几根长度较短的木条一起凑出的话,那么优先考虑用木条B来拼。因为在与其他木条拼接的过程中,几根较短的木条会比较长的木条更灵活,更有可能拼接成功。换句话说,在这种情况下,如果我们已经选择了使用木条B来与木条A拼接构成了一个目标的新木条长度,但是剩下的木条仍然不能全部拼接成功的话,就意味着木条A与其他剩下的原木条一定无法拼出目标长度了。而在实际过程中,如果上述的“木条A”是由几个更小的木条拼出来的,就意味着前面拼凑出“木条A”的流程是错的,要利用递归倒回去调整。而如果木条A就是一根完整的原木条,就意味着之前几组木条的拼接有问题,要倒回去调整。这一结论用于剪枝。
#include <iostream>
#include <algorithm>
using namespace std;
int sti[70];
int used[70];
int len,num,sum,n;
int cmp(int a,int b)
{
return a>b;
}
int dfs(int pos,int l,int fini)
{
if(fini==num)
return true;
for(int i=pos;i<n;i++)
{
if(used[i])
continue;
if(l+sti[i]==len)
{
used[i]=1;
if(dfs(0,0,fini+1))
return 1;
used[i]=0;
return 0;//剪枝
}else if (l+sti[i]<len)
{
used[i]=1;
if(dfs(i+1, l+sti[i], fini))
return 1;
used[i]=0;
if(l==0)
return 0;//剪枝
while(sti[i]==sti[i+1])
i++;//剪枝
}
}
return 0;
}
int main()
{
while(~scanf("%d",&n)&&n)
{
sum=0;
for(int i=0;i<n;i++)
{
scanf("%d",&sti[i]);
used[i]=0;
sum+=sti[i];
}
sort(sti, sti+n, cmp);
for(len=sti[0];len<sum;len++)
{
if(sum%len)
continue;
//将非法情况continue,否则求num的计算会出问题
num=sum/len;
if(dfs(0, 0, 0))
break;
}
printf("%d\n",len);
}
return 0;
}