题目描述
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
输入格式
输入包含多组数据,每组数据包括两行。
第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。
第二行是截断以后,所得到的各节木棍的长度。
在最后一组数据之后,是一个零。
输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
样例
样例输入
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
样例输出
6
5
题解
先来介绍一下剪枝这个神器。剪枝,很形象,就是帮我们的搜索去掉一些不需要的重复的东西,从而提高效率。其基本型有5种。
1.优化搜索顺序,在一些搜索问题中,搜索树的各个层次,各个分支之间的顺序是不固定的。不同的搜索顺序会产生不同的搜索树形态,其规模也相差甚远。
2.排除等效冗余,在搜索过程中,我们如果判定通过几个分支达到目的是等效的(eg.耗时耗空间相同),则我们只需要对其中一条分支进行搜索。
3.可行性剪枝(上下界剪枝),在搜索过程中,我们对当前状态进行检查,如果发现当前剪枝无法实现目的,就直接执行回溯。就好比,走迷宫的时候看到前面是死胡同就回头,而不是走到路的尽头在返回。
4.最优性剪枝,在最优化问题中,如果一个当前的代价已经超过了当前已知的最优解则沿着这条分支走下去永远都不会更新答案,此时可以直接执行回溯。
5.记忆化,可以记录每个状态的搜索结果,在重复遍历一个状态时可以直接检索并返回。
回到此题,我们来依次考虑几个剪枝。
STEP1:优化搜索顺序,把木棒长度从大到小排序,优先选择较长的木棒。
STEP2:排除等效冗余
1.限制先加入的木棒的长度是递减的。比如先放入长度为 的木棒,在放入 的木棒与先放入 ,再放入 显然是等价的,所有只需搜索其中一种。
2.对于当前原始木棒,我们记录一个最近一次尝试拼接的长度。如果分支搜索失败则无需再向该木棒尝试拼入其他相同长度的木棒。
3.如果第一个尝试拼入的木棒返回失败,则可以直接判定当前分支失败,立即回溯。EMMMM……怎么说呢,我一开始也是懵的,仔细一想它其实因为拼入这根木棒前,面对的原始木棒都是空的(还没有进行拼接),所以这些木棒都是等效的。木棒拼在当前木棒失败,拼在其他木棒也一定会失败。
4.如果在当前u原始木棒中拼入一根木棒后,木棒恰好完整,并且接下来拼接剩余原始木棒的分支返回失败,那么直接判定当前分支失败。再用贪心解释一遍,再用一根木棒恰好拼完必然比再用若干根木棒拼完更好
上述剪枝分别利用“同一木棒上顺序的等效性”,“等长木棒的等效性”,“空木棒的等效性”已经“贪心”,大大提高了搜索效率
ps.此题在输入时必须判断输入的当前木棒长度是否大于50
------------算法思路参考李煜东《算法竞赛》
the from @Stardust_AK
代码如下:
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int n,sum=0,val=-1;
int ans,m;
bool f[100005];
bool cmp(int x,int y){
return x>y;
}
bool DFS(int nowi,int nowans,int last,int len){
if(nowi>=ans)
return 1;
if(nowans==len){
return DFS(nowi+1,0,1,len);
}
int now=0;
for(int i=last;i<=m;i++){
if(!f[i] and nowans+a[i]<=len and now!=a[i]){
f[i]=1;
if(DFS(nowi,nowans+a[i],i+1,len)) return 1;
now=a[i];
f[i]=0;
if(nowans==0 or nowans+a[i]==len){
return 0;
}
}
}
return 0;
}
int main(){
while(scanf("%d",&n)!=EOF){
if(n==0)
break;
val=-1;
sum=0;
m=0;
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x>50)
continue;
m++;
a[m]=x;
sum+=a[m];
val=max(val,a[m]);
}
sort(a+1,a+m+1,cmp);
for(int i=val;i<=sum;i++){
if(sum%i==0){
ans=sum/i;
memset(f,0,sizeof f);
if(DFS(1,0,1,i)){
printf("%d\n",i);
break;
}
}
}
}
return 0;
}