题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入输出格式
输入格式:
输入文件共有二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65
(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出格式:
输出文件仅一行,表示要求的原始木棍的最小可能长度
输入输出样例
输入样例#1:
9
5 2 1 5 2 1 5 2 1
输出样例#1:
6
说明
2017/08/05
数据时限修改:
-#17 #20 #22 #27 四组数据时限500ms
-#21 #24 #28 #29 #30五组数据时限1000ms
其他时限改为200ms(请放心食用)
Q
思路:
爆搜一定不行,所以要加最优化剪枝和可行性剪枝;
在dfs中
1.un:剩多少切割后的木棍没有使用(刚开始自然是n);
2.len:还需要len长度可以凑成一个切割前的木棍(刚开始是ans);
3.ans:切割前的木棍长度(需要有技巧的枚举);
4.k:当前切割后的木棍起始的下标(可以减少for的循环次数);
目标条件:un=0且len=0;
剪枝:
- 将木棍长度从大到小排序,可以保证相同长度的木棍在一起,还可以减少搜索次数(因为从大往小选,选的次数少;如果从小往大选,选许多小的可能还构不成ans,再选大的又超了ans,这样就需要回溯多次才能得到答案);这个剪枝很高效,不加只有60分;
- 去重,如果在某个时候选择某一个切割前的木棍答案返回false,那么选择与它大小相同的肯定也是false,那就跳过;
- 如果当前切割后的木棍长度=len,但组装失败(如果成功就已经return true了)即返回false,因为从大到小排序,当前的切割后木棍明明可以组成成功,但后面的切割后的木棍却组成失败,说明后面的切割后的木棍长度不够或者直接构不成(剩下的切割后的任意长度和!=切割前的木棍长度),那么继续搜肯定不行(从大到小排的序!);
如果回溯时len =ans,但没有return true,说明当前切割后的木棍与后面的切割后的木棍无法组成切割前的木棍,有一个切割前的木棍无法构成就早早的跳出来,所以return false;
我还是太弱。。。。
自己打的代码只有69分,还有很多地方没有想到。。。。noip 2017 rp+++++;
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a[10001],maxx,sum,nxt[100001],l,r,f,last;
bool vis[10001];
bool dfs(int un,int len,int ans,int k)
{
if(!len)
{
if(!un) return true;
else len=ans,k=1;
}
for(int i=k;i<=n;i++)
{
if(vis[i] || len<a[i]) continue;
if(!vis[i-1] && a[i]==a[i-1]) continue;
vis[i]=1;
if(dfs(un-1,len-a[i],ans,i+1)) return true;
vis[i]=0;
if(len==ans || len==a[i]) return false;
}
return false;
}
bool cmp(int h,int b)
{
return h > b;
}
void solve()
{
maxx=-1,sum=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]>50)
{
n--;
i--;
}
}
for(int i=1;i<=n;i++)
{
vis[i]=0;
sum+=a[i];
maxx=max(maxx,a[i]);
}
sort(a+1,a+n+1,cmp);
for(int i=maxx;i<=sum;i++)
{
if(sum%i==0)
{
if(dfs(n,0,i,1))
{
cout<<i<<endl;
return;
}
}
}
}
int main()
{
scanf("%d",&n);
if(n==0) return 0;
solve();
return 0;
}