POJ 1011 Sticks(DFS回溯剪枝)
http://poj.org/problem?id=1011
题意:
给你N根长度可能不同的小棍子,它们是由多根(或1根)长棍子切断而成的。现在要你求出原始棍子的最小合法长度是多少?
分析:
本题是POJ2362的加强版:
http://blog.youkuaiyun.com/u013480600/article/details/27581015
用类似的做法,首先我们求出所有小棍子的长度和sum.然后原始棍子的长度L肯定要>=最长的当前棍子长.
然后让L从小到大递增(L<=sum),一一尝试.当sum%L==0时,可以dfs尝试.
其中用vis[i]表示第i根棍子是否被使用了,用
bool dfs(intcur_len, int cnt, int left, int begin)来尝试构造.其中cur_len表示本轮尝试我们假定的原始棍长,cnt表示我们正在尝试构造第cnt根原始棍,left表示当前第cnt棍子我们还差left长度需要构造,begin表示小棍子从begin位置的小棍子开始探索(关键的剪枝就在这里,定序技巧,在POJ2362我解释了)
源代码中共有5处剪枝,少了一处都可能超时.关键在于剪枝4那里,小木棍一定要从大到小排序.(从小到大排序超时) 主要原因在于:
剪枝3那里,如果一根小木棍作为当前原始木棍的第一根都没有后续匹配方案的话,那么这个小木棍不可能出现在剩下的任何方案中(想想,是不是).这里如果当前剩余小木棍长度为1,1,1,1,1,1,8. 我们需要配对的原始木棍长度为10时,那么就要递归6次才能判断出.如果小木棍长度从大到小排序的话如:8 1 1 1 1 1 1 ,那么只需要递归3次就能判断出.所以长的小木棍收敛速度更快,所以需要木棍长度从大到小排序再DFS.
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=70;
int n,sum;
int len[maxn];
bool vis[maxn];
bool cmp(int a,int b)
{
return a>b;
}
bool dfs(int cnt,int cur_len,int left,int begin)//当前构造第cnt根棍子,原始长度cur_len,还需left长度每构造,从begin位置开始选小棍子
{
if(cnt==sum/cur_len) return true;//正好构造完sum/cur_len根原始木棍
int fail=-1;//记录上一轮失败木棍的长度
for(int i=begin;i<n;i++)
{
if(vis[i] || fail==len[i]) continue; //剪枝1,fail长度已经失败一次,不用再试
vis[i]=true;
if(left==len[i])
{
if(dfs(cnt+1,cur_len,cur_len,0)) return true;
fail=len[i];
}
else if(left>len[i])
{
if(dfs(cnt,cur_len,left-len[i],i+1)) return true; //剪枝2,定序剪枝
fail=len[i];
}
vis[i]=false;
if(left==cur_len) break;//剪枝3,此时的用len[i]的小木棍作为原始棍的第一根,
//都不能组合成一根原始棍,说明len[i]不论放哪里都是没用的,后面不用再试
}
return false;
}
int main()
{
while(scanf("%d",&n)==1&&n)
{
sum=0;
int max_len=0;
for(int i=0;i<n;i++)
{
scanf("%d",&len[i]);
max_len=max(max_len,len[i]);
sum+= len[i];
}
sort(len,len+n,cmp);//剪枝4,这里一定要从大到小排序,如果从小到大就超时,与剪枝3有关
int L;
bool flag=false;
for(L=max_len;L<=sum-L;L++)if(sum%L==0)//剪枝5,循环只需找到sum/2的下取整即可
{
memset(vis,0,sizeof(vis));
if(dfs(0,L,L,0))
{
printf("%d\n",L);
flag=true;
break;
}
}
if(flag==false) printf("%d\n",sum);
}
return 0;
}