题目描述:
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
输入格式
输入包含多组数据,每组数据包括两行。
第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。
第二行是截断以后,所得到的各节木棍的长度。
在最后一组数据之后,是一个零。
输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
数据范围
数据保证每一节木棍的长度均不大于50。
输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5
分析:究极经典的剪枝题目。
从小到大去枚举每个可能的原木棒的长度,满足条件 len 为 总木棒的长度和sum的约数,然后通过dfs去判断此长度可不可行。
1、搜索顺序:递归参数为已经组成了多少根原木棒,递归函数从大到小去枚举每个木棒,去组成长度为len的原木棒。
2、剪枝与优化:
2-1、排除等效冗余:利用组合型递归枚举start,防止 123 组成原木棒 等同于 132。
2-2、可行性剪枝: 2-2-1: 当当前木棒失败,那么可以跳过和它相等长度的木棒,枚举下一根小木棒,因为一定也会失败。 2-2-2:当每根组成原木棒的第一根小木棒失败的时候,直接跳出此len方案。 2-2-3:如果当前木棍能恰好填满一根原始木棍(即组成原始木棍的最后一根小木棍),但因剩余的木棍无法组合出合法解而返回,用更短的木棍组合来代替这根木棍,他们的总长恰好是当前木棍的长度,但是由于这些替代木棍在后面的搜索中无法得到合法解,当前木棍也不可能替代这些木棍组合出合法解。因为当前木棍的做的事这些替代木棍也能做到。所以,当出现加上某根木棍恰好能填满一根原始木棍,但由在后面的搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。
2-2-4:别漏了一个简单剪枝,即当前枚举的木棍加上组合的当前长度如果大于len,可以continue跳过,不需要做下去了。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 70;
int sticks[N];
int n , sum , len ;
bool st[N];
bool dfs(int start , int s , int cnt){ //start为当前枚举的木棍,s为当前组合的长度,cnt为已经组成的木棒的根数,len为枚举的木棒长
if( cnt * len == sum ) return true;
if( s == len ) return dfs( 0 , 0 , cnt + 1);
for(int i = start ; i < n ; i ++)
{
if( st[i] || sticks[i] + s > len ) continue;
if( i > 1 && sticks[i] == sticks[i - 1] && st[i-1] == false) continue;
st[i] = true;
if( dfs(i+1 , s + sticks[i] , cnt ) ) return true;
st[i] = false;
if( s == 0 ) return false;
if( s + sticks[i] == len ) return false;
}
return false;
}
int main(){
while( cin >> n , n != 0)
{
sum = 0 ;
memset( st , false ,sizeof st);
for(int i = 0 ; i < n ; i ++)
{
scanf("%d",&sticks[i]);
sum += sticks[i];
}
//从大到小,优化搜索顺序
sort( sticks, sticks+n);
reverse(sticks,sticks+n);
len = 1;
while ( true )
{
if( sum % len == 0 && dfs(0 ,0 ,0 ) )
{
cout << len << endl;
break;
}
len ++;
}
}
return 0;
}