P1120 小木棍 [数据加强版]
原题链接
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
共二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65
(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出格式
一个数,表示要求的原始木棍的最小可能长度
输入输出样例
输入 #1
9
5 2 1 5 2 1 5 2 1
输出 #1
6
题意:给定N条小木棍的长度,将其组成几根长度相同的小木棍,要求出组成小木棍的最小长度。
思路:用dfs枚举所有可能找出最小的即可,然而这道题最重要的不是找思路,而是给dfs剪枝,6重优化缺一不可,缺一就会超时,好好学好好看。ps:本蒟蒻看了题解后写的代码,超时到宇宙爆炸。(不要忘记管理员的备注哦,不然会死的很惨很惨).
屁话少说上代码:
#include <bits/stdc++.h>
using namespace std;
int a[70],n=0,sum=0,vis[70];
inline int read()//常规操作快读.
{
int x=0,f=1;
char c;
c=getchar();
while(!isdigit(c))
{
if(c=='-')f=-1;
c=getchar();
}
while(isdigit(c))
{
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return f*x;
}
void dfs(int k,int num,int x,int last)//k为已拼好木棍根数,num为当前正在拼的木棍长度,x为预期单根长度,last为上根木棍的标号.
{
if(num==x)//拼成一根,转拼下一根.
{
k++;
last=0;//last置0,又从长木棍开始拼凑.
num=0;
}
if(k*x==sum)//完成目标输出后直接退出程序,减少不必要的逐层退出,
{
printf("%d",x);
exit(0);
}
for(int i=last; i<n; i++)
{
while(a[i]+num>x&&i<n)//优化1:剪去下一跟长度大于未完成木棍的小木棍.
i++;
if(vis[i]==0)
{
vis[i]=1;
dfs(k,num+a[i],x,i+1);
vis[i]=0;
if(num+a[i]==x||num==0)//优化2:最重要也是最难理解的,将会在题目尾部备注.
break;
while(a[i+1]==a[i]&&i+1<n)//优化3:既然该长度配对失败,下一根相同长度也不可能成功.
i++;
}
}
}
int cmp(int a,int b)
{
return a>b;
}
int main()
{
int x,i,T,max1=-1;
T=read();
for(i=1; i<=T; i++)
{
x=read();
if(x<=50)//管理员的话不听会WA的很惨很惨很惨很惨...
{
a[n++]=x;
sum+=x;
max1=max(max1,x);
}
}
sort(a,a+n,cmp);//优化4:将长度从大大小排列,从大的开始拼凑,因为小木棍比大木棍更加灵活,可选择性更高.
for(i=max1; i<=sum; i++)//优化5:原木棍长度必定大于等于砍断后最长木棍长度,小于总长度.
{
if(sum%i==0)//优化6:总长度比被单根木棍长度整除.
{
dfs(0,0,i,0);
}
}
return 0;
}
讲讲代码中备注的事,干脆借鉴大佬的解释:
当前长棍剩余的未拼长度等于当前木棍的长度时,这根木棍在最优情况下显然是拼到这(如果用更多短木根拼完剩下的这段,把这根木棍留到后面显然不如把更多总长相等的短木棍扔到后面)。如果在最优情况下继续拼下去失败了,那肯定是之前的木棍用错了,回溯改即可。
当前长棍剩余的未拼长度等于原始长度时,说明这根原来的长棍还一点没拼,现在正在放入一根木棍。很明显,这根木棍还没有跟其它棍子拼接,如果现在拼下去能成功话,它肯定是能用上的,即自组或与其它还没用的木棍拼接。但继续拼下去却失败,说明现在这根木棍不能用上,无法完成拼接,所以回溯改之前的木棍。
大佬云集的题解链接
Bye!