P1120 小木棍 [数据加强版]
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入输出格式
输入格式:
共二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中 N≤65
(管理员注:要把超过 50 的长度自觉过滤掉,坑了很多人了!)
第二行为 N 个用空个隔开的正整数,表示 N 根小木棍的长度。
输出格式:
一个数,表示要求的原始木棍的最小可能长度
我的AC过程:
一开始蜜汁出错,然后改了改代码
这就非常尴尬了.......
肝了三个小时,无数剪枝,依然66分
然后去睡觉QAQ
第三天.......
重构代码,胡乱剪枝,然后.......
好了不废话了,讲一下思路和代码
思路:
dfs遍历几乎每一种情况
一找到可行解就输出并退出程序
然后五个剪枝,代码中会详细说到
代码解释:
#include <iostream>
#include <algorithm>
using namespace std;
int n,a[77],cnt,maxn,maxnn;
bool use[77];
bool cmp(int x,int y)
{return x>y;}
inline void dfs(int ans,int sum,int ii,int now)
//ans代表现在正在已经拼好的长度
//sum代表已经拼好的根数
//ii代表每一根原先是多长(也就是答案)
//now代表现在要试着将哪一根木棍拼上去
{
if(sum*ii==maxn)//此时都拼好了,直接输出;
{
cout<<ii;
exit(0);//可以在程序的任何地方退出程序;
}
if(maxn-ans<a[cnt])//此时剩余的长度连最短的一截也拼不上,回溯到上一层:剪枝1
return;
if(ans==ii)//已经拼好了这一跟,该拼下一根
{
dfs(0,sum+1,ii,1);
return;
}
for(int i=now;i<=cnt;i++)//不需要从一遍历到cnt了,之前的已经遍历过了:剪枝2
if(!use[i] && ans+a[i]<=ii)
{
use[i]=1;
dfs(ans+a[i],sum,ii,i+1);
use[i]=0;
if(ans==0||ans+a[i]==ii)break;
//若某组拼接不成立,且此时 已拼接的长度为0 或 当前已拼接的长度与刚才枚举的长度之和为最终枚举的答案时,则可直接跳出循环。因为此时继续枚举其它更小的值时,显然可能情况更少,且同样凑不完。:剪枝3
while(a[i]==a[i+1])i++;//如果其它木棍和拼接失败的木棍一样长,那么这根木棍也必然拼接失败:剪枝4
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
if(x<=50)
{
a[++cnt]=x;
maxn+=a[cnt];//总长度
}
}
sort(a+1,a+cnt+1,cmp);
for(int i=a[1];i<=maxn/2;i++)//剪枝5
//这里为什么只需要循环到一半呢?
//一个数的因数不可能超过本数一半(因数为本数除外)
if(maxn%i==0)
dfs(0,0,i,1);
cout<<maxn;//这里解决了因数为本数的特殊情况;
return 0;
}