题目:https://www.luogu.com.cn/problem/P1120
题目描述:给你一堆木棍,拼出一些长度相等的木棍,问这个“长度”最小可以是几
题目思路:还是深搜与剪枝
首先来一个基本思路:
从小到大枚举答案再爆搜
开始剪枝:
优化1:
由于题目中说“长度不大于50的木棍”
所以在输入小木棍的时候,长度大于50的可以直接扔掉
优化2:
原始木棍再短也短不过小木棍的长度,所以答案所在范围的上界就是所有小木棍中长度最长的那个
再想,如果原始木棍只有1根,那不就是把所有的木棍加起来吗?所以答案所在范围的下界就是所有小木棍的长度之和
而后,如果原始木棍有2根,那就是所有小木棍的长度之和对半分,也就是范围下界对半分
所以我们的下界可以定为(所有小木棍的长度之和)/2,如果这都没有,那就只能是把所有木棍加起来的那种了
优化3:
如果所有小木棍的长度之和无法整除一个假定答案,那么这个答案是无论如何也无法实现的
这就可以再刷下来一批
优化4:
由于是从小到大枚举,所以搜到一个可行的答案可以直接输出然后再见
注意了!!!!优化5-8是重点!!!!
优化5:
前面的木棍我们已经看过了,所以我们没有必要再去看它们(仅限于同一根假定原始木棍中)
所以设计一个p作为这次搜索开始的起点
优化6:
如果当前剩余的长度与当前搜到的小木棍的长度相等
这就是一个运气爆棚的方案
要是还没过,那就可以再见了
优化7:
如果当前剩余的长度与假定原始木棍的长度相等
那么这货兜兜转转还是会回到优化6中出现的情况、
玩nm!
优化8:
如果这个木棍不行,那么与它同样长度的木棍都不行
全找出来,并且全踢掉(这也是我排序的理由)
代码如下
#include<bits/stdc++.h>
using namespace std;
int n,a[66],i,sum=0,l,d,v[66];
//a是一堆木棍,v是v[i]木棍是否被用过
bool cmp(int x,int y){//由大到小排序
return x>y;
}
bool dfs(int now,int p,int sti){
//now是当前正在拼的木棍的最小长度
//p是我们待会儿搜索木棍的起点
//sti是已经拼出来的木棍数量
if(sti==d){//这个数量达到了预期
return 1;//此方案可行,返回“真”
}
if(now==0){//如果这根木棍恰好拼完
if(dfs(l,1,sti+1)){//拼完的木棍(sti+1)
//再回到预期木棍长度和数组起点
//如果返回了“真”证明此方案可行
return 1;//返回“真”
}
}
for(int i=p;i<=n;++i){//优化5
if(v[i]==0&&a[i]<=now){//没有用过且拼成之后的木棍没有过长
v[i]=1;//设定“已使用”
if(dfs(now-a[i],i+1,sti)){//继续递归
//如果这个递归返回“真”,那么此方案可行
return 1;//返回“真”
}
v[i]=0;//如果没成就把木棍吐回来
if(now==a[i]||now==l){//优化6,7
break;
}
while(a[i+1]==a[i]){//优化8
i++;
}
}
}
return 0;
}
int main(){
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d",&a[i]);
if(a[i]>50){//优化1
i--;
n--;
}
else{
sum+=a[i];//找上界
}
}
sort(a+1,a+1+n,cmp);//找下界
for(i=a[1];i<=sum/2;++i){//优化2
if(sum%i==0){//优化3
l=i;
d=sum/i;
if(dfs(l,1,0)){
printf("%d",l);
return 0;//优化4
}
}
}
printf("%d",sum);
return 0;//再见
}
总结:经典剪枝题
多读读5-8的优化,会有很大收获
浙公网安备 33010602011771号