#10020. 「一本通 1.3 例 3」小木棍(典型深搜剪枝)(详解)

小木棍

题目描述
原题来自:题目链接

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

输入格式
第一行为一个单独的整数 N 表示砍过以后的小木棍的总数。 第二行为 N 个用空格隔开的正整数,表示 N 根小木棍的长度。

输出格式
输出仅一行,表示要求的原始木棍的最小可能长度。

样例输入

9
5 2 1 5 2 1 5 2 1

样例输出

6

数据范围与提示

1<=N<=60

困扰了两天的题,主要是剪枝真的不好想,当我看到题的时候,我甚至不知道从哪里下手,自己还是要多动手,不然真的会懵的@~@

AC代码:

#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
int n,mid,sum,res,ans;
int a[70],vis[70];//vis[]起标记作用,即标记哪些小木棍被用过
bool cmp(int x,int y)
{
    
    
	return x>y;
}
//len表示每一根原始木棍被砍过之后剩余的长度
//sta表示每一根原始长度的木棍被砍过一段之后需要从被砍过小木棍(排过序的已经)的下一个开始继续寻找
//now表示我们需要的几根原始小木棍
int dfs(int len,int sta,int now)
{
    
    
	int i,j;
	if (now==res)//当我们搜到的(now)原始小木棍的根数与我们本来就需要的原始小木棍的根数相等(res)
		//此时len肯定为0,因为我们now加1的条件就是len为0
		//即该if语句的真正内在语句是if(now==res&&len==0)
		return 1;
//当len==0的时候就是当前这根原始木棍已经被砍完,我们需要一根新的原始木棍,长度为(ans),即根数加1
	if (len==0)
		if (dfs(ans,1,now+1))//只有当len==0的时候now才会加1,我们还是从a[1]开始找,不用担心会重复,因为我们已经做了标记
			return 1;
	for (i=sta; i<=n; i++)
	{
    
    
		//当前这根小木棍没有被用过,并且该木棍的长度小于当前原始木棍剩余的长度
		if (!vis[i]&&a[i]<=len)
		{
    
    
			vis[i]=1;//当前这根小木棍已经用过就标记成1
			if (dfs(len-a[i],i+1,now))//1.1
				return 1;
			//	printf("%d***\n",len-a[i]);
			//	printf("%d**\n",len);
			//在返回的时候标记要回溯,即取消标记
			//【该题代码的重中之重】【搞懂剪枝就成功了】【剪枝也挺难的@-@】
			vis[i]=0;
			//剪枝1
//对于len==ans这条剪枝,可以用一组数据来说明
//例:4 4 3 3 3 3 第一次原始长度为4,前两个4都可以,当到第一个3的时候len-3=1,往后所有小木棍长度都大于1
//即1.1那if语句中的函数返回值为0,会从1.1语句往下进行,vis[i](i==3)=0,len=4
//本次的原始小木棍的长度ans也为4,即当len==ans的时候就可以直接break
//即长度(len)为4的原始小木棍不符合题意,我们要把len++开始探索5(在main()函数中进行)
			//	printf("%d***%d***%d***%d***\n",len,a[i],i,ans); 1.2
//对于函数的返回,如果你不知道返回的len、i、a[i]为多少的时候,你可以直接输出该值,例如1.2
			if (len==ans/*||len==a[i]*/)
				break;
			//剪枝2
//用当前长度的木棍搜下去得不到结果时,用一根同样长度的还是得不到结果,所以可以提前返回
//while循环要放在最后,因为我们需要木棍搜下去得不到结果,才进行相同长度木棍加加
			while(a[i]==a[i+1])
				i++;
			//【都懂了吗@-@】
		}
	}
	return 0;
}
int main()
{
    
    
	int i,j;
	scanf("%d",&n);
	for (i=1; i<=n; i++)
	{
    
    
		scanf("%d",&a[i]);
		sum+=a[i];
	}
//一根长木棍肯定比几根短木棍拼成同样程度的用处小,即短小的可以更灵活组合
//所以按照从大到小进行排序
	sort(a+1,a+1+n,cmp);
	for (i=a[1]; i<=sum; i++) //迭代搜索一步一步逼近最优解
	{
    
    
		if(sum%i!=0)
			continue;
		res=sum/i;//res代表的是当原始小木棍的长度为i时,我们需要的多少根原始小木棍
		ans=i;//ans就是当前我们找到的原始小木棍的长度
		if(dfs(ans,1,0))//初始判断的小木棍只有0根,我们的a数组下标从1开始,所以我们要从1开始找
		{
    
    
			printf("%d\n",ans);//因为我们是从最小的原始木棍开始找的,所以当我们找到后就直接输出
			return 0;
		}
	}
	return 0;
}

我觉得解释的还是挺详细的,哈哈。

猜你喜欢

转载自blog.csdn.net/zlzqq/article/details/109391894