洛谷1120 小木棍
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入输出格式
输入格式:
输入文件共有二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤60
(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出格式:
输出文件仅一行,表示要求的原始木棍的最小可能长度
输入输出样例
输入样例#1:
9
5 2 1 5 2 1 5 2 1
输出样例#1:
6
【思路】
枚举+回溯法构造判定。
不难想到从小到大枚举原木棍的长度,但是长度最大可以为300,太大了,我们换而从小到大枚举原木棍根数num,只要可以构造出num根相同长度len的木棍则完成任务。
如何判定能够构造成功?回溯法。
先对数据由大到小sort,这样在搜索的时候会先尝试长度大的木棍,显然先选大长度的木棍是优于选小长度的,因为小长度会更有用(可以组合的更多)。
以目前组装的第几根木棍d、目前组装的长度nowlen以及该从哪开始枚举木棍的下标nowi为状态,搜索是否能够使d==num+1即可。
剪枝:
1、 木棍数目枚举范围:最大为 长度和/最大长度
2、 木棍数目是否符合要求:木棍数目为总长度的约数。
3、 后缀和判断解不可行。
4、 Nowi的使用减少同一根小棒的枚举
5、 每次组装新的木棍的时候人为选择最长的木棍(加速枚举中nowlen+a[i]<len的判断,否则会多拓展一层)
【代码】
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 5 const int maxn = 60+10; 6 7 int a[maxn],cnt[maxn]; 8 int len,num,n; 9 int suff_s[maxn]; 10 11 inline bool cmp(const int& a,const int& b) { 12 return a>b; 13 } 14 bool dfs(int d,int nowlen,int nowi) { 15 if(d==num) return true; 16 17 if(len-nowlen > suff_s[nowi]) return false; 18 19 for(int i=nowi;i<n;i++) if(cnt[a[i]]) { 20 if(a[i]+nowlen < len) { 21 cnt[a[i]]--; 22 if(dfs(d,nowlen+a[i],i+1)) return true; 23 cnt[a[i]]++; 24 } 25 if(a[i]+nowlen==len) { 26 cnt[a[i]]--; 27 int j; 28 for(j=0;j<n;j++) if(cnt[a[j]]&&a[j]<len) break; 29 cnt[a[j]]--; 30 if(dfs(d+1,a[j],j+1)) return true; 31 cnt[a[j]]++; 32 cnt[a[i]]++; 33 } 34 } 35 return false; 36 } 37 int main() { 38 ios::sync_with_stdio(false); 39 cin>>n; 40 int tot=0,x,_max=0,maxx=0; 41 for(int i=0;i<n;i++) { 42 cin>>x; 43 if(x<=50) { 44 a[tot++]=x; 45 suff_s[tot-1]=x; 46 _max+=x; 47 maxx=max(maxx,a[tot-1]); 48 cnt[x]++; 49 } 50 } 51 n=tot; 52 for(int i=n-2;i>=0;i--) suff_s[i] += suff_s[i+1]; 53 sort(a,a+n,cmp); 54 int t=_max/maxx; if(t>n) t=n; 55 cnt[a[0]]--; 56 for(num=t;num>=0;num--) if(_max%num==0) { 57 len=_max/num; 58 if(dfs(0,a[0],1)) { 59 cout<<len; 60 break; 61 } 62 } 63 return 0; 64 }