题目
1819:木棒
总时间限制: 1000ms 内存限制: 65536kB
描述
乔治拿来一组等长的木棒,将它们随机地裁断,使得每一节木棍的长度都不超过50个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。
输入
输入包含多组数据,每组数据包括两行。第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。
输出
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
样例输入
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
样例输出
6
5
代码
#include <bits/stdc++.h>
using namespace std;
struct node{
int x;
int k;
bool operator<(const node& h)const{
return x>h.x;//降序
}
}s[70];
int n;
bool full(){//判定所有木棍都被选中
for(int i=0;i<n;i++)if(!s[i].k)return 0;
return 1;
}
void view(int x) {
cout<<"第"<<x<<"木棍"<<endl;
cout<<"序号\t";for(int i=0;i<n;i++)cout<<i<<"\t";cout<<endl;
cout<<"长度\t";for(int i=0;i<n;i++)cout<<s[i].x<<"\t";cout<<endl;
cout<<"标记\t";for(int i=0;i<n;i++)cout<<s[i].k<<"\t";cout<<endl;
}
bool ok(const int& total,int he,int x,int id){//从第x木棍开始,升序凑够total
if(full())return 1;//递归基,全选中就ok
if(total==he)return ok(total,0,0,id+1);//本次凑够了,但还有剩余木棍,从头下一轮
if(total<he)return 0;//超长了,本次失败
for(int i=x;i<n;i++){//遍历剩余木棍
if(s[i].k||he+s[i].x>total)continue;//剪枝,该木棍用过了,或者会超长
s[i].k=id;//标记
//view(i);
if(ok(total,he+s[i].x,i+1,id))return 1;
s[i].k=0;//回溯
if(he==0)break;//剪枝,一个都没拼,就算了
while(i+1<n&&s[i].x==s[i+1].x)i++;//剪枝,代码走到这里说明该长度木棍用不成。
}
return 0;//没成
}
int main(){
//freopen("data.cpp","r",stdin);
while(cin>>n&&n){
int sum=0,ans=0;//木棍总长,原始木棍最短长度
for(int i=0;i<n;i++){
cin>>s[i].x;
ans=max(ans,s[i].x);//找截后最长木棍(原始木棍起码是最长截后木棍)
sum+=s[i].x;
s[i].k=0;//多组数据,初始化
}
sort(s,s+n);//降序排序
for(;ans<=sum;ans++){//从最长截取后长度遍历到木棍总长
if(sum%ans!=0)continue;//剪枝,木棍总长须被原始木棍(截取前均长)长度整除
if(ok(ans,0,0,1))break;//如果截取后木棍不同组合均能拼出ans,就是答案。
}
cout<<ans<<endl;
}
return 0;
}
结果
技术细节
从截取后最长木棍开始尝试,一直到所有木棍的和
木棍总长不能被单根原始木棍整除,不可取
递归:
- 两个递归基:全都选完了,返回ok;超过原始木棍长度,返回失败;
- 递归部分有两层,一层是:拼成后,从头(可以是第二个)拼下一组 。二层是:凑够本组。
凑够本组:
- 遍历剩余木棍。
- 剪枝:用过的不能用,超长的不能用。
- 标记
- 如果继续选后能拼成,才可以。(递归作为条件,前后拼成先后顺承)
- 回溯取消标记
进一步剪枝:
- 循环第一次后,当前木棍一根都没拼上,剩余不用循环,直接失败
- 循环一次后,没有递归,就说明没有拼成,那往后一样的木棍也不用再试了。(可以放到前面剪枝,就说前面没选中,而且跟这根等长,也无需再试)
小结
递归要找
1.递归出口递归基
2.递归两个层面,接下一轮,和接下一根
3.回溯
4.剪枝