小木棍
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 50 50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入格式
第一行是一个整数
n
n
n,表示小木棍的个数。
第二行有
n
n
n 个整数,表示各个木棍的长度
a
i
a_i
ai。
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
9
5 2 1 5 2 1 5 2 1
样例输出 #1
6
提示
对于全部测试点, 1 ≤ n ≤ 65 1 \leq n \leq 65 1≤n≤65, 1 ≤ a i ≤ 50 1 \leq a_i \leq 50 1≤ai≤50。
—乐-----------------乐--------------------乐----乐----乐---------------------乐-----------------乐----------
上面是我第一次提交的记录hhh
大致思路
-
木棍长度范围:首先,原木棍长度不可能小于木棍中最长的一根,否则最长的木棍无法得出;其次,原木棍最长为所有木棍和;再者,每根木棍一定是其他木棍加和得出的,且所有木棍的和与所有木棍和的一半也一定在需枚举范围内
- 在进行dfs的枚举中,肯定是原木棍长度越小符合题意越早时间需求越省时间,所以我们从小到大枚举,但与此相反,现木棍长度从大到小枚举,因为同样长度的长木棍与组合起来的小木棍肯定是多个小木棍更为灵活,优先选择长木棍。
- 根据以上,我们可以用优先队列来实现从小到大的原木棍长度枚举,用桶来实现不重复长度入队,maxx存储最大值,summ存储现木棍和,只有长度大于maxx且小于summ的才可入队
priority_queue <int,vector<int>,greater<int> > q;
for(int i=1;i<=n;i++){
cin>>a[i];
summ+=a[i];//累加和
maxx=max(maxx,a[i]);//最大值
tong[a[i]]++;//tong来实现之后的不重复入队
}
q.push(maxx);q.push(summ/2);//最大值及最大值的一半入队(最大值一半不入队会wa...)
sort(a+1,a+1+n,cmp);//现木棍从大到小
for(int i=1;i<=n;i++){
sum=0;
for(int j=i;j<=n;j++){
sum+=a[j];
if(tong[sum]==0&&sum>maxx&&leng<=sum){
q.push(sum);//所以可能的原木棍长度
}
tong[sum]++;
}
}
- 确定了原木棍的所有可能,我们就可以for循环进行dfs了
while(!q.empty()){//队列不为空
leng=q.top();//取出队首元素
q.pop();//取出后出队
if(summ%leng==0){//只有能够整除summ的leng才可以进行dfs
if(dfs(n+1,leng,0,1)){//我们的dfs作为int型或bool,
//来判断现在的leng是否可行,之后细嗦
cout<<leng<<endl;
return 0;
}
}
}
-
dfs思路及减枝
- 我们设置四个参数,分别对应总木棍数,当前枚举长度,当前木棍还需长度,当前选择木棍位置
- 重复长度的木棍无需重复以他为起点搜索
- 当前木棍长度等于所需木棍长度,则直接break即可
- 同理,所需木棍长度等于当前原木棍长度,则break
int dfs(int nn,int len,int rest,int cnt){
//总木棍数,当前枚举长度,当前木棍还需长度,当前选择木棍位置
if(nn==0&&rest==0)return 1;//剩余木棍为0,木棍恰好也拼好,那么找到答案
if(rest==0){//拼好了一根木棍,重新开始下一个,从0开始重新拼
rest=len;
cnt=0;
}
for(int i=cnt;i<=n;i++){//从当前数到n枚举
if(flag[i]==1)continue;//若此木棍以被使用,continue
if(a[i]>rest)continue;//这个木棍大于还需要的木棍长度,continue
flag[i]=1;//使用这根木棍
if(dfs(nn-1,len,rest-a[i],i+1))return 1;//dfs
flag[i]=0;//回溯,解锁这根木棍
if(a[i]==rest)break;//若此木棍长度等于所需长度,那么无需再进行搜索,直接break
if(len==rest)break;//所需木棍长度若在搜索后还等于len,那么这个搜索不合理,直接break
while(a[i]==a[i+1])i++;//不重复搜索相等木棍
}
return 0;
}
- 若去掉减枝
if(a[i]==rest)break;
- 若去掉减枝
if(len==rest)break;
足可见减枝的重要性
AC NODE
#include<bits/stdc++.h>
using namespace std;
int n,a[99999],tong[99999],sum,maxx=-1,summ=0;//桶一定记得开大!不然会re
int leng;
bool cmp(int a,int b){
return a>b;
}
priority_queue <int,vector<int>,greater<int> > q;
bool flag[99999];
int dfs(int nn,int len,int rest,int cnt){
//总木棍数,当前枚举长度,当前木棍还需长度,当前选择木棍位置
if(nn==0&&rest==0)return 1;//剩余木棍为0,木棍恰好也拼好,那么找到答案
if(rest==0){//拼好了一根木棍,重新开始下一个,从0开始重新拼
rest=len;
cnt=0;
}
for(int i=cnt;i<=n;i++){//从当前数到n枚举
if(flag[i]==1)continue;//若此木棍以被使用,continue
if(a[i]>rest)continue;//这个木棍大于还需要的木棍长度,continue
flag[i]=1;//使用这根木棍
if(dfs(nn-1,len,rest-a[i],i+1))return 1;//dfs
flag[i]=0;//回溯,解锁这根木棍
if(a[i]==rest)break;//若此木棍长度等于所需长度,那么无需再进行搜索,直接break
if(len==rest)break;//所需木棍长度若在搜索后还等于len,那么这个搜索不合理,直接break
while(a[i]==a[i+1])i++;//不重复搜索相等木棍
}
return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
summ+=a[i];//累加和
maxx=max(maxx,a[i]);//最大值
tong[a[i]]++;//tong来实现之后的不重复入队
}
q.push(maxx);q.push(summ/2);//最大值及最大值的一半入队(最大值一半不入队会wa...)
sort(a+1,a+1+n,cmp);//现木棍从大到小
for(int i=1;i<=n;i++){
sum=0;
for(int j=i;j<=n;j++){
sum+=a[j];
if(tong[sum]==0&&sum>maxx&&leng<=sum){
q.push(sum);//所以可能的原木棍长度
}
tong[sum]++;
}
}
while(!q.empty()){//队列不为空
leng=q.top();//取出队首元素
q.pop();//取出后出队
if(summ%leng==0){//只有能够整除summ的leng才可以进行dfs
if(dfs(n+1,leng,0,1)){//我们的dfs作为int型或bool,
//来判断现在的leng是否可行,之后细嗦
cout<<leng<<endl;
return 0;
}
}
}
return 0;
}