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

文章介绍了如何使用深度优先搜索(DFS)解决一个编程问题,即根据给定的小木棍长度,找出原始木棍的最小可能长度。通过遍历可能的木棍长度和利用剪枝策略优化搜索过程,最终找到满足条件的解。代码中还包含了排序优化和数据结构的使用。

每日一宏

#define 大法师 dfs

大法师万岁

题目

题目描述

原题来自:CERC 1995
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

输入格式

第一行为一个单独的整数 N 表示砍过以后的小木棍的总数。 第二行为 N 个用空格隔开的正整数,表示 N 根小木棍的长度。

输出格式

输出仅一行,表示要求的原始木棍的最小可能长度。

样例

输入
9
5 2 1 5 2 1 5 2 1
输出
6

代码详解

#include<bits/stdc++.h>
using namespace std;
int id[65];
vector<int> vec;
int n,ans=-0x3f3f3f,len;
int gs,sum;
int cnt;
bool vis[65];
bool f;
struct cmp{//functor
	bool operator () (const int &a,const int &b){
		return a>b;
	}
};
void dfs(int,int,int);
int main()
{
	cin>>n;
	vec.push_back(0x3f3f3f);//先放进去一个特别大的数,便于后边下标从1开始
	for(int i=1;i<=n;i++){
		int tmp;
		cin>>tmp;
		if(tmp<=50){//一定要判断是否在50以内
			ans=max(ans,tmp);//找出最大值
			sum+=tmp;
			vec.push_back(tmp);//从后面放入tmp
		}
	}
	sort(vec.begin(),vec.end(),cmp());
	cnt=vec.size()-1;
	id[cnt]=cnt;
	for(int i=cnt;i>=1;i--){
		if(vec[i]==vec[i-1]){
			id[i-1]=id[i];
		}
		else{
			id[i-1]=i-1;
		}
	}
	for(int i=ans;i<=sum/2;i++){
		if(sum%i==0){//原始木棍的长度肯定是sum的因数
			f=0;
			len=i;
			vis[1]=1;//默认一定选第一根
			gs=sum/i;//根数
			dfs(1,1,len-vec[1]);
			if(f){
				cout<<i;
				return 0;
			}
		}
	}
	cout<<sum;
	return 0;
}
void dfs(int k,int last,int rest){//k为当前分到第几根木棍,last为上一次分到哪个木棍,rest是凑完这根木棍还需要的长度
	int i;
	if(!rest){//如果rest到0,那么这根木棍找完了
		if(k==gs){//如果k到了总根数,则找到答案
			f=1;
			return;
		}
		for(i=1;i<=cnt;i++){//找到一个没标记的木棍
			if(!vis[i]){
				vis[i]=1;
				break;
			}
		}
		dfs(k+1,i,len-vec[i]);//找下一根木棍
		vis[i]=0;//回溯
		if(f){//有答案就return
			return;
		}
	}
	for(i=last+1;i<=cnt;i++){//顺着上一次的继续找
		if(vec[i]<=rest&&!vis[i]){//找rest能减去的
			break;
		}
	}
	for(;i<=cnt;i++){
		if(!vis[i]){
			vis[i]=1;
			dfs(k,i,rest-vec[i]);
			vis[i]=0;//回溯
			if(f||rest==vec[i]){//大剪枝
				return;
			}
		}
		i=id[i];//大剪枝
	}
}

一眼大法师
首先,我们要遍历原始小木棍的长度
范围是从最长的小木棍的长度开始,一直到sum/2(想一想,为什么)
遍历到sum/2后,再往后除了sum本身就没有sum的因数了,所以只需要遍历到sum/2
如果到sum/2还没有答案
那就输出sum(一段也不分)
接下来又到了快乐的剪枝环节
根据题意得出以下剪枝方案:

剪枝①

id[cnt]=cnt;
for(int i=cnt;i>=1;i--){
	if(vec[i]==vec[i-1]){
		id[i-1]=id[i];
	}
	else{
		id[i-1]=i-1;
	}
}

id这个数组的作用是指向连续相等的数中最后一个的下标
比如说我已经将样例排好序了,id的值如下表

123456789
vec555222111
id333666999

因为同一长度的木棍效果是相同的,
所以一个不行,后面的都不行,所以要跳过

i=id[i];

剪枝②

if(rest==vec[i]){
	return;
}

如果说此时rest等于vec[i],
就不必再搜了(先前已经搜了一遍,因为rest减去vec[i]等于0,所以进行了一次判断,开始下一个木棍的搜索),直接return掉

优化①

struct cmp{
	bool operator () (const int &a,const int &b){
		return a>b;
	}
};

此代码名为functor,是sort排序速度唯一能超过快排的方法
是快排速度的一倍多,所以特别推荐

优化②

vector<int> vec;
vec.push_back(0x3f3f3f);
vec.push_back(tmp);
sort(vec.begin(),vec.end(),cmp());
cnt=vec.size()-1;

vector浅显易懂
不仅随便从前从后进数
而且可以随便从前从后删数
还是无限大的数组
稍微一定义就可以当数组使用
具体用法可以看这位大佬

小结

一本通上是可以ac的
但洛谷94分过不了不知道为啥
欢迎大佬暴打本蒟蒻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值