问题描述 :
小明有一些相同长度的木棍,他将它们随机切割,直到所有切割后的短棍长度不超过50。现在他想把棍子恢复到原来的状态,但他忘了他原来有多少根棍子(但知道至少有两根),也不知道它们原来有多长。请帮助他,设计一个程序,计算出这些棍子的最小原始长度。
输入说明 :
输入包含多组数据,每组数据包含2行。
第一行包含切割后的短棍的数量n,n<=64。
第二行包含这些短棍的长度,由空格分隔。所有长度值都是大于0的整数。
最后一组数据之后是一个0,表示输入结束。
输出说明 :
为每组数据输出一行,包括一个整数,表示棍子原始的长度。
如果原始长度有多种可能,则输出最小的那个。
输入范例 :
7
2 7 7 7 7 10 20
6
1 2 3 11 11 20
0输出范例 :
30
24
算法思路:
这是一题深搜剪枝的题,剪枝操作主要体现在,将stick数组进行从大到小进行排序,可以加快算法速度,遍历所以可能的木棍的原始长度,这里的一个巧妙的操作是,从stick中最大的元素开始枚举,一直到所有stick数组元素的所有值的和,这样就减少了一些不必要的木棍原始长度的枚举了。
并且在dfs时,减少对相同元素的重复遍历。可以凑出目标长度时,退出枚举。
进行dfs操作时,结束条件是:组合的target长度的木棍的个数==stick总和/木棍目标长度
具体实现看代码注释。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int n;
int stick[66];
int sum=0;
int visited[66];//标记是否枚举
/*
target代表要求的长度
now代表现在所拼好的长度
count代表需要拼好的个数
x代表该次枚举的木棍编号
*/
bool dfs(int x,int target,int now,int count)//x当前枚举的最小长度--now现在已经拼完的长度--sum现在拼完的根数
{
if(count==sum/target) //当拼好的木棍数==需要拼好的木棍数时 ,返回true
return true;
if(now==target)//当当前拼好的木棍长度达到所需木棍长度时,如果此时之后的小木棍可以拼出要求的木棍长度,返回true
{
if(dfs(1,target,0,count+1)) //dfs深搜 下一个元素
return 1;
}
for(int i=x;i<=n;i++)//枚举x之后的小木棍
{
if(!visited[i]&&now+stick[i]<=target)//下标为I的元素没有被访问过,并且现在已经拼接完的长度小于等于目标长度
{
visited[i]=1;//标记访问过
if(dfs(i+1,target,now+stick[i],count)) //进入下一次dfs(现在拼接的根数,下一个元素下标,目标长度,现在已经拼接的长度加上stick[i]木棍的长度,因为两者加载一起还没有超过target)
return true;
visited[i]=0;//回溯恢复标记值
//剪枝操作
if(stick[i]==target-now||now==0) //如果现在拼接的木棍的长度为0,或者此时截断的木棍长度==目标长度-现在拼接的木棍长度,跳出循环.可以凑出要求木棍长度时退出枚举
break;
while(stick[i]==stick[i+1]) //排除相同项的重复遍历
i++;
}
}
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>stick[i];//输入stick
sum+=stick[i];//计算木棍总和
}
sort(stick+1,stick+1+n,greater<int>());//从大到小进行排序
for(int i=stick[1];i<=sum;i++)//从断开的小木棍中的最大值开始遍历,一直到sum的长度,这是遍历所有可能的原始木棍的长度。
{
if(sum%i!=0)//如果不能整除,直接排除 剪枝操作,最后组装的木棍个数,肯定是总长度sum与木棍原始长度整除得到
continue;//直接跳过这个长度的枚举
if(dfs(1,i,0,0))//深搜递归,下标从1开始,因为输入的时候就是从下标1开始存储木棍的长度的。
{
cout<<i;//i is answer。 because we want a number which is smaller
break;//跳出
}
}
return 0;
}