P1120小木棍题解

前言

这道题细节优化比较多本人做了5h分两段4,1,终于A了,写一篇题解。

题目

链接

https://www.luogu.com.cn/problem/P1120

字面描述

题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 5050。

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

输入格式
第一行是一个整数 nn,表示小木棍的个数。
第二行有 nn 个整数,表示各个木棍的长度 a_ia
i

输出格式
输出一行一个整数表示答案。

样例数据

输入输出样例
输入 #1复制
9
5 2 1 5 2 1 5 2 1
输出 #1复制
6
说明/提示
对于全部测试点,1 \leq n \leq 651≤n≤65,1 \leq a_i \leq 501≤a
i

≤50。

思路

此题若直接DFS满篇黑送给你,所以我们要适当加入一些剪枝优化

  1. 答案范围 最长木棍长度~总长度/2,总长度
  2. 枚举长度时如果总长度%原长度==0继续否则下一个
  3. 拼接长度时按照从大到小的顺序
  4. 每次拼接找与其剩余长度相符者,二分
  5. 一遍不能拼接回溯

重点

木棍长度大于50,不能算

代码实现

#include<bits/stdc++.h>
using namespace std;

const int maxn=100+10;
int n,cnt,sum,mx,m,nl;
bool all;
bool vis[maxn];
int a[maxn],nxt[maxn];
//从大到小
inline bool cmp(int x,int y){return x>y;}
//dfs
inline void dfs(int k,int end,int leave){//拼接第几根,上次访问,剩余长度
	if(!leave){
	//拼完
		if(k==m){
			all=true;
			return;
		}
		int op;
		//找最大的
		for(int i=1;i<=cnt;i++){
			if(!vis[i]){
				op=i;
				break;
			}
		}
		vis[op]=true;
		dfs(k+1,op,nl-a[op]);
		vis[op]=false;
		if(all)return;
	}
	int l=end+1,r=cnt;
	//二分小于剩余长度的最大值
	while(l<r){
		int mid=(l+r)/2;
		if(a[mid]>leave)l=mid+1;
		else r=mid;
	}
	//dfs
	for(int i=l;i<=cnt;i++){
		if(!vis[i]){
			vis[i]=true;
			dfs(k,i,leave-a[i]);
			vis[i]=false;
			if(all)return;
			if(leave==a[i]||leave==nl)return;
			i=nxt[i];
			if(i==cnt)return;
		}
	}
}
int main(){
	scanf("%d",&n);
	//输入
	for(int i=1;i<=n;i++){
		int b;
		scanf("%d",&b);
		if(b>50)continue;
		a[++cnt]=b;
		mx=max(mx,b);
		sum+=b;
	}
	//排序
	sort(a+1,a+cnt+1,cmp);
	//记录最后与他相同的值
	nxt[cnt]=cnt;
	for(int i=cnt-1;i>=1;i--){
		if(a[i]==a[i+1])nxt[i]=nxt[i+1];
		else nxt[i]=i;
	}
	//枚举
	for(int i=mx;i<=sum/2;i++){
	//余数不为0,继续
		if(sum%i!=0)continue;
		m=sum/i;
		nl=i;
		vis[1]=true;
		dfs(1,1,nl-a[1]);
		vis[1]=false;
		if(all){
			printf("%d\n",nl);
			return 0;
		}
	}
	printf("%d\n",sum);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

materialistOier

我只是一名ssfoier

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值