题意:乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50.现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度.给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度.
分析:首先读入的时候把长度大于50的自动过滤掉,剩下n根长度小于等于50的木棍,设这n根小木棍的长度和为(tot),长度最大的为(val).
我们直接从小到大枚举原始木棍的长度,并通过dfs判断是否合法即可.枚举的下界是一个小木棍的最大长度(val),上界是长度和(tot).设当前枚举的原始木棍的长度为(len),那么(tot)必然要能被(len)整除,否则不合法,若能够被整除,显然我们要是能够拼凑出(sum=tot/len)根长度为(len)的木棍就是合法答案.
接下来就只要考虑dfs的剪枝操作了.
一,我们把木棍长度从大到小排序,优先考虑较长的木棍,显然能减少状态数.
二,既然剪枝一已经把木棍排序了,那么我们干脆固定每次填入的木棍长度是递减的.这个只要dfs的变量增加一个last,表示当前从第last根木棍开始考虑就好了.
三,假设我们给一根"空"的 填入木棍,也就是当前的木棍长度(cnt=0),那么当填入(a[i])后该搜索状态不合法,那么直接判定当前分支不合法,因为你把(a[i])填入剩下的其它的"空"的,都会是不合法的.
四,跟三类似,如果我们当前填完了(a[i])之后,填完了一个长度为len的木棍,发现该搜索状态不合法,那么直接判定当前分支不合法,因为你再用别的若干根短的木棍来代替(a[i]),那么接下来搜索下一根木棍还会是不合法的.
五,如果填入(a[i])不合法,如果(a[i+1]=a[i])那么直接不要考虑填.
这些剪枝真的是缺一不可.亲测~~~
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=105;
int n,tot,val,sum,len;
int a[N],visit[N];
//当前正在拼第now跟木棒
//第now跟木棒已经拼出了长度cnt
//当前已经考虑到了第last根木棍
inline bool dfs(int now,int cnt,int last){
if(now>sum)return true;//sum根木棒都拼完了
if(cnt==len)return dfs(now+1,0,1);//当前这根拼完了
int fail=0;//剪枝五用的
for(int i=last;i<=n;++i){
if(!visit[i]&&cnt+a[i]<=len&&fail!=a[i]){
visit[i]=1;
if(dfs(now,cnt+a[i],i+1))return true;
fail=a[i];//记录不要填与a[i]相同长度的木棍了
visit[i]=0;//回溯
if(!cnt||cnt+a[i]==len)return false;//剪枝三,四
}
}
return false;
}
int main(){
int nn=read();
for(int i=1;i<=nn;++i){
int b=read();
if(b<=50){//过滤不合法的木棍
a[++n]=b;tot+=b;//记录长度和
val=max(b,val);//比较最长木棍
}
}
sort(a+1,a+n+1);reverse(a+1,a+n+1);//从大到小排序
for(len=val;len<=tot;++len){//枚举原始木棍长度为len
if(tot%len)continue;//不能整除肯定不合法
sum=tot/len;//要拼凑出sum根木棍
memset(visit,0,sizeof(visit));//初始化
if(dfs(1,0,1)){
printf("%d
",len);
break;
}
}
return 0;
}