POJ 1011 Sticks (DFS +强力剪枝 (经典))

Sticks
Time Limit: 1000MS
Memory Limit: 10000K
Total Submissions: 120211
Accepted: 27813

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

Central Europe 1995


题目链接  :http://poj.org/problem?id=1011


题目大意  :给若干根切断的木条,用完全部所给木条拼成n根等长的原始木条,求所拼原始木条的最短长度


题目分析  :DFS + 剪枝 ,本题限制时间为1000ms,剪枝不到位绝对超时,下面列出几条不可少的剪枝

1. 设有n根木条枚举能拼成的长度为sum/n~1,这里n必须是sum的约数否则无解,找到了则为最优解,因为均分的越多长度就越短

2.按长度从大到小排序,最长的一根长度必然小于sum/n,否则无解,从长的开始取,因为长的灵活度比较低,之后便于剪枝。

3.若搜索时某两根的长度相同,第一根没取那第二根也不会取

4.每次取的木条加上取之前的长度要小于sum/n

5.之前的POJ2362  http://blog.csdn.net/tc_to_top/article/details/38460259  没加这个剪枝跑的110ms加上后0ms飘过,这题则必须加这条剪枝 :如果当前长度cur为0,当前取的最长木条为stick[i],结果stick[i]拼接失败则我们可以直接退出搜索,当前情况无解,因为如果某次当前最长的木条没被选上,则之后木条数更少了它更不会被选上,因为我们是按照降序排列的


这里再给一组数据:
64
40 40 30 35 35 26 15 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 43 42 42 41 10 4 40 40 40 40 40 40 40 40 40 40 40 40 40
40 25 39 46 40 10 4 40 40 37 18 17 16 15 40 40 40 40 40 40 40 40

答案: 454

这组数据用我的0ms过的代码跑了3,4秒,只能说POJ数据略水,该组数据要有1秒内过的求教


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 64;
int vis[MAX];
int stick[MAX];
int num, one, div_num;

bool cmp(int a, int b) 
{
    return a > b;
}
//cur代表当前某根木条的拼接长度,other代表未用木条,count代表拼成的根数
bool DFS(int cur, int other, int count)
{
	if(count == div_num) //若拼成的根叔等于份数,则找到返回
		return true;
	for(int i = other; i < num; i++)
	{
		//若当前供选长度与前一个相等且前一个未使用则不考虑当前这根
		if(i && !vis[i-1] && stick[i] == stick[i-1])
			continue;
		if(cur + stick[i] <= one && !vis[i])
		{
			//若加上小于原始长度将其标记为已使用继续搜索
			if(cur + stick[i] < one)
			{
				vis[i] = 1;
				if(DFS(cur + stick[i], i + 1, count))
					return true;
				//若之前方案不可行,将当前这根标记为未使用
				vis[i] = 0;
				//这是个很重要的剪枝,若当前选择的最长长度得不到解,该方案必然无解
				if(cur == 0)
					return false;
			}
			//若加上等于原始长度将其标记为已使用拼成一根,开始拼下一根
			if(cur + stick[i] == one)
			{
				vis[i] = 1;
				if(DFS(0, 0, count + 1))
					return true;
				//若之前方案不可行,将当前这根标记为未使用
				vis[i] = 0;
				//这是个很重要的剪枝,若之前的DFS(0,0,count+1)有一个不满足
				//则该方案必然无解
				return false;
			}
		}
	}
	return false;
}

int main()
{
	int  sum;
	while(scanf("%d",&num) != EOF && num)
	{
		sum = 0;
		memset(vis,0,sizeof(vis));
		for(int i = 0; i < num; i++)
		{
			scanf("%d",&stick[i]);
			sum += stick[i];
		}
		sort(stick, stick+num, cmp);  //从大到小排序
		 //从份数最多的枚举起,则找到一组解就可以退出,因为它必为最短
		for(int i = num; i > 0; i--)
		{
			if(sum % i == 0)   //每根原始木条的长度必须是整数
			{
				one = sum / i;  //一根原始木条的长度
				//若当前的原始木条短于最长的切断的木条长度则原始木条值非法
				if(stick[0] > one) 
					continue;
				else
				{
					div_num = i; //份数
					if(DFS(0,0,0)) //找到一个可行解就退出
						break;
				}
			}
		}
		printf("%d\n",one);
	}
}



你可能感兴趣的:(poj,DFS)