题干大意:
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入描述:
第一行为一个单独的整数N表示砍过以后的小木棍的总数。第二行为N个用空格隔开的正整数,表示N根小木棍的长度。
输出描述:
输出仅一行,表示要求的原始木棍的最小可能长度。
备注:
1≤N≤60
题目分析:
- 按木棍长度从大到小排序,便于后续处理
- 因为直接求不出来,所以枚举长度
len(范围:a[0]~sum)
,化查询为判断 - 由此得出tle的dfs:
#define fo(i,n) for(int i=0;i<n;++i)
bool dfs(int num,int len,int rest){
//还剩几根小棍没用,枚举的木棍长度,当前在拼的木棍还剩多长
if( (rest==0)&&(num==0) ) return true;
if(rest==0) rest=len;
fo(i,0,n){
if(v[i])continue;//如果小木棍用过了
if(a[i]>rest)continue;//如果这根小木棍不能用
v[i]=true;
if(dfs(num-1,len,rest-a[i])) return true;
v[i]=false;//回溯(如果这根小棍不能拼成的话,就把这根小棍拆了)
}
return 0;
}
不想TLE,于是开始优化之路
- 回溯后加上代码:
if(len==rest)break;
(因为这个木棒是去拼某个len的第一个木棒) - 难点,下文有具体解释: 如果回溯后发现这个木棒是去拼某个len的第最后一个木棒,则该len一定不合法,即
if(a[i]==rest)break;
- 跳过相同木棒,即
while(a[i]==a[i+1]) i++;
- 搜索顺序的优化:我们可以按照小木棍的长度进行排序,从大到小,这样比其它顺序快(大块一定比小块需要搜索的次数少)
- 如果当前在拼的木棍用了x根棍子,那么之后的dfs从x+1开始找就行(很巧妙),但注意如果更新了当前在拼的木棍,之后的dfs还要从第一根棍子开始找
- 写一下枚举len时的优化:
sort(a,a+n,cmp);//从大到小排序
for(int i=a[0];i<=sum;i++)
if(sum%i==0){
if(dfs(n,i,0,0)) {
cout<<i<<endl;
exit(0);//注意是退出程序
}
}//循环节保障>1根棒,那么初始每个棍长一定<=sum/2
printf("%d\n",sum);//一根棒时相加即可
第二点较难,给出具体证明:
用 反 证 法 : ( 有 贪 心 的 感 觉 ) 用反证法:(有贪心的感觉) 用反证法:(有贪心的感觉)
当进行到 ∑ a 0 , a 1 , a 2 , … i , … j , … m , … n , … \sum _{a_{0}}, a_{1}, a_{2}, …i,… j, …m, …n,… ∑a0,a1,a2,…i,…j,…m,…n,…时,假设恰有目标长度 s u m = ∑ a 0 + a 1 , sum=\sum _{a_{0}}+a_{1}, sum=∑a0+a1,( a 1 a_{1} a1就是去拼某个len的第最后一个木棒)但其为失败搜索,则 ∑ a 0 + a 1 \sum _{a_{0}}+a_{1} ∑a0+a1之后应当退出
假设 a 1 = = m + n + ∑ 1 a_{1}==m+n+\sum _{1} a1==m+n+∑1( ∑ 1 \sum _{1} ∑1代表可能的其它数据),将 a 1 a_{1} a1替换为 m + n + ∑ 1 m+n+\sum _{1} m+n+∑1,若有成功搜索,则肯定有某个和为 ∑ a 0 \sum _{a_{0}} ∑a0,可以找 i , j , … i,j , … i,j,…满足 ∑ a 0 = i + j + ∑ 2 \sum _{a_{0}}=i+j+\sum _{2} ∑a0=i+j+∑2 ( ∑ 2 \sum _{2} ∑2同样代表可能的其它数据)
但是它在 a 1 a_{1} a1使用完之后就应该搜索到了一种情况: s u m = m + n + ∑ 1 + i + j + ∑ 2 sum=m+n+\sum _{1}+i+j+\sum _{2} sum=m+n+∑1+i+j+∑2,这就产生了矛盾
所以如果使用更短的小木棍,即使存在某个也能填满该原始木棍的方法,该方法也一定不会比用大木棍更有希望获得可行解
AC代码:
#include<bits/stdc++.h>
#define LL long long
#define fo(i,a,b) for(int i=a;i<b;i++)
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
using namespace std;
int v[65];//visit:标记小棍
int a[65];//小棍长度
int n,sum;
bool dfs(int num,int len,int rest,int now){
//还剩几根小棍没用,枚举的木棍长度,当前在拼的木棍还剩多长,去拼某个len的第几位
if( (rest==0)&&(num==0) ) return true;
if(rest==0) rest=len,now=0;
for(int i=now;i<n;i++){
if(v[i])continue;
if(a[i]>rest)continue;
v[i]=true;
if(dfs(num-1,len,rest-a[i],i+1)) return true;
v[i]=false;//如果这根小棍不能拼成的话,就把这根小棍拆了
if(a[i]==rest)break;//剪枝(去拼某个len的第最后一个木棒)
if(len==rest)break;//剪枝(去拼某个len的第一个木棒)
while(a[i]==a[i+1]) i++;//剪枝(跳过相同木棒)
}
return 0;
}
bool cmp(int a,int b){return a>b;}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
sum+=a[i];
}
sort(a,a+n,cmp);//从大到小排序
for(int i=a[0];i<=sum;i++)
if(sum%i==0){
if(dfs(n,i,0,0)) {
cout<<i<<endl;
exit(0);
}
}
printf("%d\n",sum);
return 0;
}