P1120 小木棍 [数据加强版](poj 1011)
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入输出格式
输入格式:输入文件共有二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65
(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出格式:输出文件仅一行,表示要求的原始木棍的最小可能长度
输入输出样例
9
5 2 1 5 2 1 5 2 1
6
说明
2017/08/05
数据时限修改:
-#17 #20 #22 #27 四组数据时限500ms
-#21 #24 #28 #29 #30五组数据时限1000ms
其他时限改为200ms(请放心食用)
Solution:
本题就是纯考搜索和剪枝了,在看lyd的书时看到了这题,于是来写一写,结果lyd的思路还过不了这题,但剪枝的思想值得借鉴。
首先就是说说这题的坑点:注意木棍长度不大于50,所以输入的木棍长度中要忽略长度超过50的木棍,这我被坑了啊。
再来说说解题思路,搜索时从小到大枚举原木棍长度,显然(木棍最大长度≤原木棍长度≤木棍总长度),然后搜索判断情况能否可行。
然后就来说说最最重要的剪枝吧:
1、优化顺序,dfs判断情况时从最长的木棍枚举到最短的木棍,因为能与最长的木棍组成当前需要的长度的木棍的个数显然要比长度短的少,能减少分枝数。因为长度不超过50,于是没必要对木棍长度快排,而是直接桶排,常数小且方便判断和回溯。
2、木棍个数应该为整数,所以枚举的长度应该能整除总长度。
3、若当前搜索时已经使用了长度为x的木棍,则下次直接从长度为x从大到小枚举,因为显然比x长的都不可行了,否则当前就不会使用x了。
4、若某次搜索拼接时,当前拼好的长度为0或当前长度加上先前枚举的长度等于需要长度,直接跳出循环,因为我们是递减枚举,显然再往后搜不能再从那些长度小的木棍中拼出当前的长度。
代码:
#include<bits/stdc++.h> #define il inline #define ll long long #define N 100 using namespace std; int n,a[N],tmp[N],maxn,minn=N,cnt; il void dfs(int res,int now,int len,int p) { if(!res){printf("%d",len);exit(0);} if(now==len){dfs(res-1,0,len,maxn);return;} for(int i=p;i>=minn;i--) if(tmp[i]&&i+now<=len){ tmp[i]--; dfs(res,i+now,len,i); tmp[i]++; if(!now||now+i==len)break; } return; } int main() { scanf("%d",&n); int x,sum=0; while(n--){ scanf("%d",&x); if(x<=50){ a[++cnt]=x; maxn=maxn>x?maxn:x; minn=minn<x?minn:x; tmp[x]++;sum+=x; } } x=sum>>1; for(int i=maxn;i<=x;i++) if(sum%i==0)dfs(sum/i,0,i,maxn); cout<<sum; return 0; }