前言
这道题细节优化比较多本人做了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满篇黑送给你,所以我们要适当加入一些剪枝优化
- 答案范围 最长木棍长度~总长度/2,总长度
- 枚举长度时如果总长度%原长度==0继续否则下一个
- 拼接长度时按照从大到小的顺序
- 每次拼接找与其剩余长度相符者,二分
- 一遍不能拼接回溯
重点
木棍长度大于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;
}