如果我们把所有木棒从大到小排序,其实相当于制定了一个规则:在尝试拼出一根大木棒时,先挑大的(因为如果先拼大的都不行,那么先拼小的也一定不行)。
于是当我们尝试拼出第n+1根大木棒时,如果把第一次尝试就失败了(也就是把当前最长的那根作为第一根拼进去这种拼法),则没有必要再继续尝试下去(也就是找一根比他短的作为第一根),返回第n根木棒。因为在拼成这一根之后,拼下一根时又会用同样的拼法,但此时小木棒反而少了,就更不可能成功了。
反证法:如果用k-x根可以拼成功且那x根可以拼成一根,则k根也一定成功。但一定要在上述规则下进行。
同理,当我们尝试拼出第n根大木棒时,如果最后一次尝试失败了,也没有必要再继续下去。
反证法:如果一根木棒放在某个长度为L 的木棒的最后一个位置并且沿着这条路走不下去,如果用更小的木棒填充它所在的位置,则这根木棒要出现在后拼出的长度为L 的木棒中,此时交换这个木棒与此前替换它的更小的木棒组合,拼接效果不变,这就产生了矛盾。
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<map>
#include<algorithm>
using namespace std;
bool concatenate(int, int, int, int); //递归函数,判断某个长度是否是木棒可能的原始长度
bool cmp(int a,int b) {
return a>b;
}
int sticks[100]; //记录每个木棒的长度,按长度降序排列
bool used[100]; //记录木棒被使用的情况
int main() {
int n;
while(scanf("%d", &n),n) {
int i, sum = 0, len; //sum 是所有木棒的总长度
for(i = 0; i < n; i++) { //初始化,读入木棒长度
used[i] = false;
scanf("%d", &(sticks[i]));
sum += sticks[i];
}
sort(sticks,sticks+n, cmp); //将木棒按长度降序排列
len = sticks[0]; //len 的最小可能取值等于木棒中最长的一段
for(i = len; i <= sum; i++) { //按升序枚举木棒的可能长度
if (sum % i != 0 ) continue;
//如果该长度不能整除木棒总长度,则尝试下一可能长度
if ( concatenate(n, n, 0, i) ) {
//用递归函数判定n 根木棒是否可能是i 长度的原始木棒削出来的
printf("%d\n", i);
break;
}
}
}
}
bool concatenate(int totalSticks, int unusedSticks,int left, int len) {
//totalSiticks 是木棒的总数,
//unusedSticks 是未被拼到长度为len 的原始木棒中的木棒数目
//left 是当前正在拼得木棒的剩余长度
//len 是正在尝试的原始木棒长度
int i;
// 成功的情形只有一种:木棒用完的时候,最后一根也恰好被拼完。
if(unusedSticks == 0 && left == 0) return true;
if(left == 0) left = len; //如果当前木棒剩余为0,则开始拼装一个新的原始长度木棒
for(i=0; i < totalSticks; i++) { //从头到尾寻找可用的木棒
if(used[i] == true) continue; //如果已经用了,跳过
if(sticks[i] > left) continue; //如果长度比当前的空余大,跳过
used[i] = true; //否则尝试把这个木棒拼入正在尝试的木棒
if(concatenate(totalSticks, unusedSticks - 1, left - sticks[i], len)) return true;
//unusedSticks 和left 都减小,向下递归
used[i] = false; //退出上次尝试的木棒,准备尝试下一个木棒
if(sticks[i] == left || left == len) break;
//如果当前尝试的是某个原始木棒中的第一个位置或最后一个位置,
//并且导致最终失败,则不必再在这个位置上尝试余下的木棒
}
return false;
}