参考资料
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 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。
思路
搜索
定义cnt
表示原始木棍的数量,len
表示每根原始木棍的长度,sum
表示木棍的总长度。
定义状态(num, pre, last)
,其中num
表示当前正在拼的原始木棍的编号,pre
表示上一个使用的小木棍的编号,last
表示当前原始木棍的剩余长度。
- 如果
last
为0,说明当前原始木棍已经拼接完成。此时,如果有num
和cnt
相等,说明所有木棍拼接成功;如果num
和cnt
不相等,则继续拼接下一个原始木棍。 - 如果
last
不为0,则选择尚未使用的小木棍来拼接该原始木棍。
优化
优化1:仅需要考虑len <= sum/2
的情况。
优化2:当前状态为:完成了某个原始木棍的拼接,准备进入下一个原始木棍的拼接。在这一状态下,我们选取一根最长的、尚未使用过的小木棍来进行拼接,如果拼接失败,则无需再考虑其他木棍,直接从当前状态返回。这是因为,所有未使用过的小木棍最终都要被用来拼接成原始木棍,所以选择先从哪根小木棍开始都是一样的。也就是说,当前状态并不能导出拼接成功的状态。
优化3:当前状态为:尚未完成该原始木棍的拼接,且选择了一个长度恰好为last
的小木棍。如果上述选择导致拼接失败,则无需再考虑其他小木棍,直接从当前状态返回。这是因为,如果当前状态能导出一个拼凑完成的状态,那么这个长度为last
的木棍在之后也必须要被使用。显然,在剩余小木棍较多的当前状态下,选择长度为last
的小木棍都不能完成拼接,在后面剩余小木棍更少的状态下,度为last
的小木棍一定也不能完成拼接。也就是说,当前状态并不能导出拼接成功的状态。
优化4:使用二分查找的方法,找到第一个长度<= last
的小木棍的编号,然后从该编号开始选择。需要特别注意的是,二分查找的左边界可以优化为pre+1
。
优化5:预处理Next[x]
数组,存储第一个长度< x
的小木棍的下标。
代码实现
#include<bits/stdc++.h>
#define maxn 70
using namespace std;
int n;
int a[maxn];
int sum = 0;
int M = 0;
int m = 0;
bool vis[maxn];
int Next[maxn];
bool flag = false;
int cnt, len;
bool cmp(int x, int y){
return x>y;
}
//num:当前正在拼的是第几个原木棍
//pre:上一个使用的小木棍的编号
//last:拼完这个原木棍,还需要的长度
void dfs(int num, int pre, int last){
//当前原木棍拼成功了
if(last==0){
//所有木棍拼凑成功
if(num==cnt){
flag = true;
return;
}
else{
for(int i=1;i<=n;i++){
if(vis[i]) continue;
vis[i] = true;
dfs(num+1, i, len-a[i]);
vis[i] = false;
if(flag) return;
return; //优化2
}
}
}
else{
//二分查找第一个长度小于剩余长度的小木棍
int L=pre+1, R=n, mid, pos=n;
while(L<=R){
mid = (L+R)>>1;
if(a[mid]<=last){
pos = mid;
R = mid-1;
}
else{
L = mid+1;
}
}
for(int i=pos;i<=n;i++){
if(vis[i]) continue;
vis[i] = true;
dfs(num, i, last-a[i]);
vis[i] = false;
if(flag) return;
if(last==a[i]) return;
if(a[i]==m) continue; //优化3
else i = Next[a[i]]-1;
}
}
}
int main(){
memset(vis, 0, sizeof(vis));
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sum += a[i];
}
sort(a+1, a+n+1, cmp);
M = a[1];
m = a[n];
//预处理next
int temp = a[1];
for(int i=2;i<=n;i++){
if(a[i]!=temp){
Next[temp] = i;
temp = a[i];
}
}
for(int k=M;k<=sum/2;k++){
if(sum-(sum/k)*k!=0) continue;
flag = false;
vis[1] = true;
cnt = sum/k;
len = k;
dfs(1, 1, k-a[1]);
vis[1] = false;
if(flag){
cout<<k;
return 0;
}
}
cout<<sum;
return 0;
}