POJ1011 Sticks

Sticks
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 98517 Accepted: 22352

Description

George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.

Input

The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.

Output

The output should contains the smallest possible length of original sticks, one per line.

Sample Input

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

Sample Output

6
5

Source

 

 

 

解题思路:搜索+剪枝。剪枝的几个思路如下:

1.由于原始的棍子是所有的长度都一样的,所以原始的棍子长度只可能是那些能够整除总长度的数。因此我们先计算出所有棍子的长度之和,然后对每一个能够整除该和的数值进行判断,看它是否可能是原始长度。

2.由于现在的棍子都是从原始棍子切割得到的,所以原始棍子一定不会短于现在的最长的棍子。

如果对于某个待判断的原始长度可能数值L,以上两条都满足,那么接下来就要通过回溯搜索的方法看是否能够将现有的这些棍子拼接成一个一个的长度为L的棍子。如何搜索呢?先在现有的棍子集合中找到一些拼出第一个长度为L的棍子,然后再拼第二个,然后第三个...这样依次下去,直到全部拼好,或者发现不能拼凑下去。

这里还有一些剪枝策略:

1.对于一个新的需要拼凑的目标,只需要随意找一个棍子作为它的第一个部件,如果该棍子作为它的第一个部件不成功,则其他的也必定不成功。

2.对于一个需要拼凑的目标,如果他缺少的部分正好是sticks[i](即L=now+sticks[i]),那么就直接将sticks[i]作为其最后一个部件,而不需要搜索其他可能了,即:如果sticks[i]放到这里不成功,那么换用比sticks[i]短的一个或多个棍子也必定不会成功。

由于第2条的存在,我们需要先对sticks[i]数组从大到小排序。

另外,在拼凑一个原始棍子的过程中,选择棍子的顺序是按数组从前向后选的,即:如果选择sticks[i]的时候已经选择了sticks[j],那么必须要j<i才能选择i,这样也是为了剪枝,或者说是避免重复搜索。

最后,还有一个小的trick:我们在凑一个个的L的时候,需要凑多少个呢?是sum/L个吗?不需要!只需要凑够(sum/L)-1个就行了,因为剩下的那些棍子就构成了最后的一个长度为L的棍子。不过这个好像也对剪枝帮助不大。

 

/*
 * main.cpp
 *
 *  Created on: 2012-7-20
 *      Author: wwf
 */
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

bool judge(vector<int> &sticks, vector<bool> &visited, int left, int L, int count, int sum, int index);

int main() {

	while (true) {
		int n;

		cin >> n;

		if (n == 0)
			break;// end of input
		vector<int> sticks(n);
		int sum=0;
		int _max=0;
		for (int i = 0; i < n; i++) {// read lengths of the sticks got after cutting
			cin >> sticks[i];
			sum+=sticks[i];
			_max=max(_max,sticks[i]);
		}

		sort(sticks.begin(),sticks.end(),greater<int>());

		for(int L=_max;L<=sum;L++){
			//cout<<"L = "<< L <<endl;
			vector<bool> visited(sticks.size(),false);

			if(sum%L==0&&judge(sticks,visited,L,L,0,sum,0)){
				cout<<L <<endl;
				break;
			}
		}

	}

	return 0;
}

bool judge(vector<int> &sticks, vector<bool> &visited, int left, int L, int count, int sum, int index) {

	if (L*(count+1)==sum)//只需要凑够sum/L-1个就够了,剩余的棒子长度之和自然是L个了。
		return true;//凑够了
	for(int i=index;i<sticks.size();i++){
		/*
		 * 有三种情况不需要遍历sticks[i](即不能再使用sticks[i]了):
		 * 1.sticks[i]已经被使用过了
		 * 2.sticks[i]添加之后会超过需要凑够的数L
		 * 3.该木棒之前的一根木棒与该木棒值相等,却没有被使用。这种情况主要是避免多个长度相同的木棒中任选一个所造成的冗余搜索。
		 */
		if(visited[i]||sticks[i]>left||(i>0&&sticks[i]==sticks[i-1]&&!visited[i-1]))continue;
		/*
		 * 如果没有以上情况,则可以进行搜索考察
		 * 这里有一个特殊情况:如果sticks[i]加上之后刚好凑够一个L(即sticks[i]==left),那么就不用搜索j>i的情况了(对于任意的j>i,sticks[j]<=sticks[i])。这个很容易证明。
		 */
		if(sticks[i]==left){//刚好凑够,不需要再看其他选择了
			visited[i]=true;

			bool result=judge(sticks, visited, L, L, count+1, sum, 0);
			visited[i]=false;
			return result;
		}else if(left==L){//从头开始,就随便拿一个就行了
			visited[i]=true;
			bool result=judge(sticks, visited, left-sticks[i], L, count, sum, i+1);
			visited[i]=false;
			return result;
		}else{//没有凑够,普通情况
			visited[i]=true;
			bool result=judge(sticks, visited, left-sticks[i], L, count, sum, i+1);
			visited[i]=false;
			if(result)return true;
		}
	}
	return false;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值