题目链接
标准数据洛谷P1120
弱数据47.112.32.87
题目
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
第一行是一个整数
n
n
n,表示小木棍的个数。
第二行有
n
n
n 个整数,表示各个木棍的长度
a
i
a_i
ai。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入
9
5 2 1 5 2 1 5 2 1
输出
6
说明/提示
对于全部测试点, 1 ≤ n ≤ 65 1 \leq n \leq 65 1≤n≤65, 1 ≤ a i ≤ 50 1≤a_i≤50 1≤ai≤50。
题意
有n个被截断的小木棍,需要将它们连接成若干个长度相等的大木棍(小木棍必须拼完,不能剩余)
求大木棍的最小长度
思路
先枚举大木棍的长度
然后用dfs枚举是否可以用截断的木棍来拼出整数个大木棍,如果可以,求出最短的大木棍即可
但纯dfs对于这道题肯定会超时的,所以我们要对dfs进行剪枝,将时间复杂度降下来。对于此题,可以从最优性和可行性剪枝上分析
最优性剪枝
- 因为要拼出整数个大木棍并且不能剩余,所以大木棍的长度要被所有小木棍的长度总和所整除
- 所有大木棍的长度肯定要大于等于截断的小木棍中长度最大的那根
可行性剪枝
- 对小木棍的长度进行降序排序,一根长的木棍肯定比几根短的木棍要更快拼完大木棍,但不容易组合,所以由大到小排序,先拼大部分,然后再用短的木棍灵活匹配,这样会更快速地拼完
- 因为 1 中已对截断的小木棍进行降序排序,所以当我们枚举了第i个小木棍后,就不用枚举编号为 1 ∼ i − 1 1\sim i-1 1∼i−1的木棍了,而下一次枚举从 i + 1 i+1 i+1即可
- 排除等效冗余:对于当前木棍,记录最近一次尝试拼接失败的木棍,因为它失败了,那么肯定之后不能尝试再次凭借和他长度一模一样的木棍。因为他们是一模一样,没有任何差别,那么A死了,后面的A自然也得死,虽然他们下标不一样。
- 若加入第i根小木棒后超过了大木棒的长度,那就需要转而枚举更小长度的小木棍
- 如果放第一根木棒和放最后一根木棒都失败了,那么该长度一定不合法。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,target,l[66],max_start,cnt,s;
bool vis[66];
bool dfs(int x,int sum,int last){
if(x>cnt){//判断边界
printf("%lld",target);
exit(0);//结束程序
}
if(sum==target)return dfs(x+1,0,0);//拼完了一根大木棒,继续拼下一根,累加长度清零,从头开始枚举
int same=0;//可行性剪枝3
for(int i=last+1;i<=n;i++){//可行性剪枝2
if(!vis[i]&&!(l[i]==same)&&sum+l[i]<=target){//!(l[i]==same)为可行性剪枝3 sum+l[i]<=target为可行性剪枝4
vis[i]=true;
if(dfs(x,sum+l[i],i))return true;//拼接第i根小木棒
vis[i]=false;
same=l[i];//可行性剪枝3
if(sum==0||sum+l[i]==target)return false;//可行性剪枝5
}
}
return false;
}
bool cmp(int x,int y){
return x>y;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&l[i]),s+=l[i],max_start=max(max_start,l[i]);
sort(l+1,l+1+n,cmp);//可行性剪枝1
for(int i=max_start;i<=s;i++){//最优性剪枝2
if(s%i)continue;//最优性剪枝1
cnt=s/i,target=i;//
dfs(1,0,0);//当拼接成功,退出循环并输出
}
return 0;
}
总结
进行深度优先搜索时,需注意剪枝,常用剪枝的方式有:排除等效冗余、可行性剪枝、最优性剪枝、记忆化等