POJ木棒

如果我们把所有木棒从大到小排序,其实相当于制定了一个规则:在尝试拼出一根大木棒时,先挑大的(因为如果先拼大的都不行,那么先拼小的也一定不行)。
于是当我们尝试拼出第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;
}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值