poj 1011 Sticks(搜索+剪枝)

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

题意: 给出一定数量的小木棒的长度,它是由等长的若干木棒随意砍断所得到的。对于给定的一组小木棒,请求出原始木棒的可能的最小长度。

上网看了好几篇题解,觉得这个写的最简单易懂原文链接

思路:

        1.看到这个题,就是要把小棒一个个进行比较,看能不能恢复若干个原棒。但这样枚举效率太低。

         2.这里用深度优先搜索来遍历(递归实现)速度较快。

         3.假设原始木棒的长度已定,那么由若干小棒来组成一根这样的棒后,其它的原棒将不再由这些已经用过的小棒来拼接了。我们可以设置visited数组来标记这些已经“成功”拼接的木棒,而不是已经用过的木棒。

         4.那么原始木棒长度有多长,不至于从0一直往后试探吧?这样太麻烦。因为长度最小的原棒,它的长度不会比目前最大的小棒还短。长度最大的原棒的长度不会比所有小棒的长度和还长。这样范围就确定了。

         5.我们用深搜用什么参数呢?

                    首先应该是对某个可能存在的长度(原棒长)进行试探性的验证,看是否能由这所有的小棒组成。那么这个参数是必须的,记为len。

                    然后,每当“成功”接收一根小木棒(前提是这根小木棒的接收能导致一根完整的木棒被拼出,而不是暂时被接收),那么可用的小木棒数就会相应减1.这个参数很重要(记为num)。

                    再就是对我们要试探的原木棒可能取值,每当它成功接收一根小木棒,还需要用来拼接一根原木棒的长度就会减小。这也是搜索过程必须知道的量,当它减小到0时,说明一根原木棒拼接完成,它将重新被赋值为len,从而进行下一根木棒的搜索。可知这个参数同样很重要(记为remains_len)。

        6.由于过程中要确定某根小棒是否确定成功被接收,它就得提前预知加入这根小棒后其它的小木棒能不能匹配成功,就叫要求在遍历某个小木棒时,对其后的木棒进行递归搜索(深搜的特点),若能匹配成功,则标记当前小木棒为用过,可以直接返回(试探成功);若不能匹配,说明此棒目前不可用,将标记取消,待下一次搜索用。若当前木棒不可用,那么与这根小木棒长度相同的木棒也将不可用,直接跳过(剪枝),而且若这个小木棒的长度刚好是reamins_len的长度,那么更能说明后面的不能匹配了,因为如此合适的小棒被接收都不能导至试探成功,后面的小棒更不可能,直接返回0(试探失败 )(剪枝)。还有就是如果len=remains_len(说明这是新一根原棒,还没有进行匹配),而在预先判断匹配与否时已经判断不能匹配,这样都不能匹配,那么说明以后都不能匹配了(这就是深搜的效果了)。返回0(试探失败)(剪枝)。

       7.当remains_len=0&&num=0说明能用的棒已经没有,而且拼成一根原棒还需的长度为0,只能表示原棒已经完整的由这所有的小棒拼接出来。此时只需返回len(试探成功),这个操作应该放在函数最前。

      8.如果深搜完成,仍未返回试探成功,到了函数的最后,只能说明这个试探失败。直接返回0.

      9.为了让用过但没有被成功接收的小木棒再次利用,所以函数里对每个小棒进行一次搜索。以让这些漏网的小木棒或许能被再次利用。

      10.注意:当换一个原木棒长度进行试探时,要置visited为0,否则会与上次搜索混在一起

不过第6条的一点一开始没明白,就是这个剪枝:if(len==remains_len) break;这里,去了之后就会超时,说明这个剪枝很强大,后来看看了另一篇题解明白了

将开始搜索一支长为 L 的木棍时,我们总是以当前最长的未
*               新一轮刚开始,如果搜索不成功,那么以比它短的开始
*                那么也一定不能取得全局的成功。因为每一支题目给出的木棍
*                都要被用到。
*                如果,有 
*                    5
*                    5 4 4 3 2
*                  想拼成长为 6 的木棍,那么从 5 开始, 但是显然没有能与 5
*                  一起拼成 6 的,那么我们就没必要去尝试从 4 开始的,因为
*                  最终 5 一定会被遗弃。在拼第 2 3 ... 支木棍时,一样。

自已好好体会一下

下面贴上代码 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int stick[65],n;
int visited[65];
int cmp(const void *a,const void *b)
{
	return *(int *)b-*(int *)a;
}
int dfs(int len,int remains_len,int num)
{
	//len:原木棒的可能取值
	//remains_len:拼成原木棒还需要的木棒长度
	//num:当前可用小木棒的个数
	if(remains_len==0&&num==0){
		return len;
	}
	else if(remains_len==0){
		remains_len=len;
	}
	for(int i=0;i<n;i++){
		if(visited[i]==1) continue;
		if(remains_len>=stick[i]){
			visited[i]=1;//暂时标记为已用
			if(dfs(len,remains_len-stick[i],num-1)){
				//成功接收小木棒并完成任务 
				return len; 
			} 
			visited[i]=0;//没完成任务,重置成未使用
			if(stick[i]==remains_len||len==remains_len)
				break;
			while(stick[i]==stick[i+1]) i++; /*当stick[i]不能完成任务,与它相同的小棒也不能完成任务(剪枝) */
		}
	}
	return 0;
}
int main()
{
	
	while(scanf("%d",&n),n){
		int sum=0;
		memset(stick,0,sizeof(stick));
		for(int i=0;i<n;i++){
			scanf("%d",&stick[i]);
			sum+=stick[i];
		}	
		qsort(stick,n,sizeof(stick[0]),cmp);
		int ans;
		for(int len=stick[0];len<=sum;len++){
			memset(visited,0,sizeof(visited));//每次重新尝试开始都要将小木棒全置为可用
			if(sum%len==0){
				ans=dfs(len,0,n);
				if(ans){
					printf("%d\n",ans);
					break;
				}
			}
		}
	
	}
	return 0;
}
/*
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
*/


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值