题目传送门:https://www.acwing.com/problem/content/description/169/
题目大意:
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。注意: 数据中可能包含长度大于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;
int a[66];
bool vis[66];
int len,sum,val,n,cnt;
void init(){
memset(a,0,sizeof(a));
sum = 0;
len = 0;
val = 0;
}
void read(){
int t = 0,x=0;
cin >> n;
if(n == 0) return;
for(int i = 1;i<= n;i++){
scanf("%d",&x);
if(x > 50) continue;
a[++t] = x;
sum = sum + a[t];
val = max(val,a[t]);
}
n = t;
}
bool dfs(int sticks,int cab,int last){
//sticks:表示当前处理第几根新棒子了。
//cab:表示当前这个棒子已经拼接到多长了。
//last:表示用到了第几根砍断的棒子了
if(sticks > cnt) return true;//当拼接完cnt根新的等长棒子就说明成功了。
if(cab == len) return dfs(sticks + 1,0,1);//当当前这根棒子已拼至len长度,则开始处理新的一根待拼接的棒子
int fail = 0;
for(int i = last; i<= n; i++){
if(!vis[i] && cab +a[i] <= len && fail != a[i]){
vis[i] = true;
if(dfs(sticks,cab + a[i],i+1)) return true;
vis[i] = false;
fail = a[i];//记录当前失败的小木棍长度,后面碰到相等的小木棍要拼接时肯定失败,供及时剪掉用
if(cab == 0 || cab + a[i] == len){ //对等效情况及时剪枝。此剪枝不容易想到。
return false;
}
}
}
return false;//当所有分支均尝试但都失败。
}
void solve(){
sort(a+1,a+1+n);
reverse(a+1,a+1+n);
for(len = val ; len <= sum; len ++){
if(sum % len != 0 ) continue;
cnt = sum / len;
memset(vis,0,sizeof(vis));
if(dfs(1,0,1)) break; //从小到大枚举的长度,那么当只要能拼成长度一致为len的必为此题的解。
}
cout << len<< endl;
}
int main(){
while(1){
init();
read();
if(n == 0)break;
solve();
}
return 0;
}