一本通 小木棍
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
第一行为一个单独的整数 表示砍过以后的小木棍的总数。 第二行为 个用空格隔开的正整数,表示 根小木棍的长度。
输出格式
输出仅一行,表示要求的原始木棍的最小可能长度。
样例
样例输入
9
5 2 1 5 2 1 5 2 1
样例输出
6
思路在代码注释里,第一次理解剪枝,真让人头大...思路混乱ing
#include <string.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
int n,mid,sum,res,ans;
int a[70],vis[70];
bool cmp(int x,int y)
{
return x>y;
}
int dfs(int len,int sta,int now)//原长木棍剩下多长,第几根,以匹配的组数(一组就是这些木棍共同拼成了一根原长木棒)
{
int i,j;
if (now==res)//如果顺利完全匹配的根数==原本木棍的根数,成功
return 1;
if (len==0)//这一根原木棒剩余长度为0了
if (dfs(ans,1,now+1))//这一根匹配完了,下一根重新从1开始找是否有匹配。没事有vis[]呢。
return 1;
//----------正式匹配区---------------------------------------------------------------------------
for (i=sta; i<=n; i++)
{
if (!vis[i]&&a[i]<=len)
{
vis[i]=1;
if (dfs(len-a[i],i+1,now))//这一根可以用,趁着剩余长度(len)不为零,继续找下一根
return 1;
//如果幸运地连续搜到的几根长度加起来等于原长,那就一路返回1了
//一旦有不符合条件的,看见下面的return 0没?这条路径毁了;
vis[i]=0;
//-------------来吧,开森剪枝,反正你想不到-------------------------------
//出现剪枝的条件一定是某个棍不匹配,需要继续寻找匹配项
if (len==ans||len==a[i]) break;
/*1.第一根木棒是不成功的(中途有一个木棍怎样都匹配不上了,
看着那么多小木棍,就是不能让len==0,那只有放弃这个组合了,重新从第一根开始组合)
2.最后一根木棒不成功
len==a[i],剩余长度等于a[i],也就是说在这一步之前,上面的dfs为dfs(0,i+1,now),然后len==0,
最后dfs(ans,1,now+1)失败,说明这一根之后再无可匹配的木棍,返回0
怎么感觉这两个剪枝差不多啊,而且去掉第二个剪枝照样可以AC,去掉第一个剪枝就超时了
*/
while(a[i]==a[i+1])
i++;//去重剪枝
//如果这一根不成立,那么长度和它一样的肯定也不成立
}
}
return 0;
}
int main()
{
int i,j;
scanf("%d",&n);
for (i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
sort(a+1,a+1+n,cmp);
for (i=a[1]; i<=sum; i++)
{
//枚举,最长的木棍到总长度sum之间肯定有一个最小值是木棍原长
if(sum%i!=0) //由于是几根一样原长的木棍,所以最后sum一定可以被原长整除
continue;
res=sum/i;//如果i是原长,那么res就是原来有几根原长的木棍。
ans=i;// 原长,不多说
if(dfs(i,1,0))//以i为原长搜索,如果成功,那最小原长度就是i了
{
printf("%d
",i);
return 0;
}
}
return 0;
}
/*
剪枝1:
不要在同一位置多次尝试相同长度的木棒
剪枝2:
如果以后拼接失败,需要重新调整第i根棍子的拼法,
则不用考虑替换第i根棍子中的第一根木棒。
如果在不替换第一根木棒的情况下,怎么都无法成功,那么就要推翻第i-1根棍子的拼法。
如果不存在第i-1根棍子,那么就推翻本次假设的棍子长度,尝试下一个长度。
剪枝3:
不要希望通过仅仅替换已拼好棍子的最后一根木棒就能够改变失败的局面。
剪枝4:
拼每一根棍子的时候,应该确保已经拼好的部分,长度是从长到短排列的。
*/