#10020. 「一本通 1.3 例 3」小木棍

小木棍

题目描述

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 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 1n65 1 ≤ a i ≤ 50 1 \leq a_i \leq 50 1ai50

—乐-----------------乐--------------------乐----乐----乐---------------------乐-----------------乐----------

第一遍提交结果

上面是我第一次提交的记录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;
}

THE END (–我真的太喜欢减枝啦–)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值