Sticks DFS+剪枝

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 file 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

真的是一道很细节的题目,参考了网上大神的一些办法,主要难点在于各种剪枝。题目大意是说,给你一些已经折断的小木条,这些小木条是由一些等长的长木条拆出来的,问长木条的最短情况是多少。

首先,有这样几个规律:①所有长木条的长度一定是总长度的因子,这一点无需多想 ②长木条的长度,一定大于等于没根小木条的长度 ③如果一个长度的小木条不能凑出来长木条,那么和这根木条等长度的木条肯定都不行 ④构造每一根木棍时,第一根选择的一定是最长的那根 ⑤当假定的木棒长度比小木棒总和的一半还要大时,说明长度只能是总和,即所有小木棒拼成一个⑥一支长度为 K 的完整木棍,总比几支短的小木棍拼成的要好。这一条有点玄学,应该算是规律,网上的一位大神如是说“如果我要拼 2 支长为8的木棍,第一支木棍我拼成 5 + 3然后拼第二支木棍但是失败了,而我手中还有长为 2 和 1 的木棍,我可以用 5 + 2 + 1 拼好第一支,再尝试拼第二支,仔细想一想,就会发现这样做没意义,注定要失败的。 我们应该留下 2+1 因为 2+1 比 3 更灵活。”这一点在搜索的剪枝中用的到

那么基于这几个规律,加上DFS,可以写出代码,尤其是剪枝的部分,细节处理一定要仔细,否则会一直超时。

AC代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int num[55];
int n;
int vis[55];
bool cmp(int x,int y)
{
	return x>y;
}
int dfs(int len,int left,int nn)//当前假定的木棍长度,还差多少就可以凑齐这一根,剩余的木棍数目 
{
	if(left==0&&nn==0)//全部小棍拼接完成,符合条件 
		return len;
	if(left==0)//拼好了一根,再拼下一根 
		left=len;
	for(int i=0;i<n;i++)
	{
		if(vis[i]==1) continue;
		if(left>=num[i])
		{
			vis[i]=1;
			int flag=dfs(len,left-num[i],nn-1);//在选择了这根的基础上,再继续找其余的木棍 
			if(flag!=0)
				return len;
			vis[i]=0;
			if(num[i]==left||len==left)
//这一步剪枝十分重要,对应第六条规律
//len==left则是说的第四条规律,我们数组是按照从大到小排的,如果最大的那根不符合条件,剩下的
//所有情况就都不符合条件
				break;
			while(num[i]==num[i+1])
				i++;
		}
	}
	return 0;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		int sum=0,ans;
		for(int i=0;i<n;i++)
		{
			scanf("%d",&num[i]);
			sum+=num[i];
		}
		sort(num,num+n,cmp);
		for(int i=num[0];i<=sum;i++)
		{
			if(sum%i==0)
			{
				memset(vis,0,sizeof(vis));
				ans=dfs(i,0,n);
				if(ans!=0)
					break;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ayakanoinu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值