【POJ1011】Sticks-DFS+调整法剪枝

测试地址:Sticks

题目大意:有n根小棍,要拼成若干根长度相等的大棍,问大棍的最小长度是多少。

做法:很容易确定搜索思路:枚举大棍的长度,然后进行DFS查看是否可行,用一个bool函数dfs(r,l)表示当前剩余r根小棍未使用,当前大棍剩余长度为l的情况下是否有解。但是单纯这样裸搜,不知道要搜多少年呢...于是在看了一些资料之后,找到了一些剪枝思路:

1.如果当前大棍长度不是小棍长度之和的因数,不用考虑(应该是很容易就能想到的剪枝)。

2.大棍长度不会小于最长小棍的长度,并且如果在大棍长度≤小棍长度之和/2的范围中如果无解,则结果就已经可以确定为小棍的长度之和(即将所有小棍拼成一根大棍),所以枚举范围为:最长小棍长度≤大棍长度≤小棍长度之和/2。

3.如果将小棍按长度由大到小排序,搜索的时间会显著减少。

4.在开始拼一根新的大棍时,要选还未被使用的最长的小棍作为最开始的小棍,如果发现以这根小棍开始不能拼成这样的大棍,则直接退回去调整上一根大棍。

5.用used数组来表示小棍使用过与否,如果在从小棍标号从小到大枚举的过程中,发现现在的小棍长度和上一根小棍长度相等,且上一根小棍没被使用,则判定为无解,因为如果有解则一定已经在搜索前一根小棍时就出解了。

6.为预防重复搜索,应记录最近拼接的小棍编号no,只用枚举编号比no大的即可(如果是开始拼接一根新的大棍,参照剪枝4)。

7.如果剩余长度等于当前所枚举的小棍长度,则说明该小棍正好填满这根大棍,但如果继续搜索后返回的结果是无解,则退回枚举上一根小棍。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,len[65],tot,no,i;
bool used[65];

bool cmp(int a,int b)
{
  return a>b;
}

bool dfs(int r,int l)
{
  if (r==0&&l==0) return 1;
  if (l==0) l=i;
  int start=1;
  if (l!=i) start=no+1; //剪枝6
  for(int j=start;j<=n;j++)
    if (!used[j]&&len[j]<=l)
	{
	  if (j>1&&!used[j-1]&&len[j-1]==len[j]) continue; //剪枝5
	  used[j]=1;no=j;
	  if (dfs(r-1,l-len[j])) return 1;
	  used[j]=0;
	  if (len[j]==l||l==i) return 0; //剪枝4,7
	}
  return 0;
}

int main()
{
  while(scanf("%d",&n)&&n)
  {
    tot=0;
	for(i=1;i<=n;i++)
	{
	  scanf("%d",&len[i]);
	  tot+=len[i];
	}
	sort(len+1,len+n+1,cmp); //剪枝3
	for(i=len[1];i<=tot/2;i++) //剪枝2
	  if (!(tot%i)) //剪枝1
	  {
	    memset(used,0,sizeof(used));
		no=1;
		if (dfs(n,i))
		{
		  printf("%d\n",i);
		  break;
		}
	  }
	if (i>tot/2) printf("%d\n",tot);
  }
  
  return 0;
}


posted @ 2016-10-01 11:01  Maxwei_wzj  阅读(90)  评论(0编辑  收藏  举报