P1120 小木棍 [数据加强版]:
题目描述:
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式:
共二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65
(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出:
一个数,表示要求的原始木棍的最小可能长度
测试样例:
输入
9
5 2 1 5 2 1 5 2 1
输出
6
思路:
一道比较复杂的题目(主要难度在于对代码进行剪枝),先输入所有木棍的长度,从其中最大的开始搜索,判断以当前长度是否所有木棍能拼接成,如果可以就输出当前长度,退出循环。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n,a[70],c[70],m;
int sum=0,maxmum=0,len;
bool flag,b[70];//数组b用来表示该位置的小木棍有没有被使用组合原始木棍。
void dfs(int t,int last,int rest)
{
if(rest==0)
{
if(t==m-1)//显然如果已经有m-1根满足了,最后一根必定满足,不需要继续组合,直接输出长度
{
cout<<len<<endl;
exit(0);//退出程序
}
int k=n;
for(int i=1; i<=n; i++)
{
if(!b[i]//下一次搜索时,从第一根没有被使用过的木棍开始
{
k=i;
break;
}
}
b[k]=1;//标记
dfs(t+1,k+1,len-a[k]);//下一个情况
b[k]=0;//回溯
return;
}
int l=last,r=n,mid;
while(l<r)//使用二分,找到第一根长度小于等于原始木棍长度的木棍
{
mid=(l+r)/2;
if(a[mid]<=rest)r=mid;
else l=mid+1;
}
/*如果当前长棍剩余的未拼长度等于当前木棍的长度或原始长度,继续拼下去时却失败了,就直接回溯之前拼的木棍。*/
for(int i=l; i<=n; i++)
{
if(!b[i]&&a[i]<=rest)
{
b[i]=1;
dfs(t,i+1,rest-a[i]);
b[i]=0;
if(rest==a[i]||rest==len)
{
return;
}
i=c[i];
i--;
}
}
}
int main()
{
int t,x;
cin>>t;
for(int i=1;i<=t;i++)
{
cin>>x;
if(x>50)
{
continue;//按照题目要求,当小木棍的长度大于50时候,不储存到数组中
}
a[++n]=x;
sum+=a[n];//统计所有成功输入的木棍的长度和
if(maxmum<a[n])
{
maxmum=a[n];//得到所有木棍长度中最长的那根,作为原始木棍的最小长度(如果原始木棍的长度小于最长木棍的长度,那显然是不满足题意的)
}
}
sort(a+1,a+n+1,greater<int>());//便于下一个优化
for(int i=1; i<=n; i++)
/*很重要的一个优化。在搜索的时候,如果某一长度被确定无法被使用,那么后续一些等长的木棍就不需要再进行尝试了,直接从下一个长度的木棍继续尝试。*/
{
int j=i,k=i;
if(k<n&&a[j]==a[k])
{
while(k<n&&a[j]==a[k])
{
k++;
}
c[j]=k;
}
else
{
c[j]=k+1;
}
}
for(len=maxmum;len<=sum/2;len++)
{
int temp=sum%len;//只有当原始木棍的长度能够被木棍总长度整除的时候,才有可能满足题意,否则不可能,应直接跳过进行下一个枚举
if(temp!=0)
{
continue;
}
m=sum/len;//计算得到一共可以组成多少根原始木棍
memset(b,false,sizeof(b));
dfs(1,1,len);
}
if(!flag)
/*如果上一步的搜索都没有得到正确的输出,那么就输出所有木棍的长度和,即所有木根合在一起拼接成一根木棍。*/
{
cout<<sum<<endl;
}
return 0;
}