题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入
第一行是一个整数 n,表示小木棍的个数。
第二行有 n 个整数,表示各个木棍的长度 ai。
对于全部测试点,1≤n≤65,1≤ai≤50。
输出
输出一行一个整数表示答案。
输入输出样例
输入1:
9
5 2 1 5 2 1 5 2 1
输出1:
6
思路
观察可以发现,题目中n达到了65,如果使用普通的DFS深搜会TLE ,使用BFS广搜则会MLE。
那么如果使用多个剪枝(包括但不限于优化搜索顺序,排除等效冗余,可行性和最优性)****, 是有可能通过的。
这里采用一种新的DFS叫做迭代加深搜索(IDS)。
IDS(迭代加深搜索) 是一种结合了深度优先搜索(DFS)空间效率与广度优先搜索(BFS)完备性的搜索策略。
它通过逐步增加深度限制进行多轮DFS。每一轮都在当前深度限制内进行搜索,若未找到解,则增加深度限制并重新开始搜索。
其优势在于避免了DFS陷入过深分支的冗余,同时规避了BFS存储全部中间状态的内存开销。虽然存在重复搜索浅层节点的开销,但在状态空间未知或深度较大的问题中,它是一种非常有效的平衡选择。
所以,本题比较合理的做法是IDS。
一个近似的IDS框架:
cpp
#include<bits/stdc++.h>
using namespace std;
int n,ans;
void dfs(int k,/*其他参数*/int maxdep)//当前看k层,最大搜索层数maxdep
{
if(k>maxdep)//超出范围
{
if(/*满足某样的条件*/)
{
ans=/*......*/;
}
return
}
if(sum>m)
{
return;
}
for(/**/)
{
dfs(k+1,maxdep);//下一层
}
}
int main()
{
cin>>n;
/*
......
*/
for(int d=1;d<=n;d++)//d:最大搜索层数 (这里假设到n)
{
dfs(1,d);
if(ans>0)
{
break;
}
}
cout<<ans;
return 0;
}
来看一下过程:
STEP 1:读写优化,输入。为方便计算,这里进行预处理,计算最大最小长度,并统计这种木棍有多少。
STEP 2:枚举原始木棍长度(主函数内准备调用IDS)。
原棍长度 l 必须满足:
l >= maxlen(不能比最长段还短) sumlen % l == 0(整除才可能拼成整数根)从小到大枚举,第一个找到的就是最小可能长度。
STEP 3:开始写DFS.参数包括:
剩余段数n, 当前拼的长度len, 目标长度sticklen, 可尝试的最大段长度mx
STEP 3-1:DFS出口:若全部段用完且最后一段恰好拼完,说明找到解。否则若当前这根拼完(但没有全部拼完),则重置len=0,重新DFS
STEP 3-2:如何选择:在可试长度应 ≤ 剩余所需长度 sticklen-len,且不超过 mx(排除等效冗余)的基础上,从大到小尝试,加快找到可行组合。
STEP 3-3:回溯+剪枝:
如果当前是某根原棍的开头,放入段 i 失败,则相同长度段放在开头都会失败,直接 break 不再试更短段(因为排序尝试,后续更短段不可能更好)。
如果当前段放入后刚好拼满这根原棍,但后续搜索失败,则回溯。因为这是"最优填满"仍失败,无需再试其他段来填满这根棍子。
代码
cpp#include<bits/stdc++.h> using namespace std; int n,a,sumlen,maxlen,minlen=100,b[70];//b[i]:长为i的木棍数量 bool hasans; //n:还剩下木棍数量 //len:一拼成木棍长度 //sticklen:原木棍长度 void dfs(int n,int len,int sticklen,int mx) { /*剪枝1:全局最优剪枝-一旦找到答案,立即终止所有搜索*/ if(hasans) { return; } /*终止条件:所有木棍已用完*/ if(n==0) { if(len==sticklen) { hasans=true; } return; } /*剪枝2:当前木棍拼满剪枝-当前原棍拼满,立即开始下一根*/ if(len==sticklen) { dfs(n,0,sticklen,maxlen); return; } /*剪枝3:搜索范围限制-只尝试不超过剩余长度和mx的长度*/ /*剪枝4:搜索顺序优化-从大到小尝试,减少分支*/ for(int i=min(mx,sticklen-len);i>=minlen;i--) { if(b[i]>0) { b[i]--; dfs(n-1,len+i,sticklen,i); b[i]++;//回溯恢复状态 /*剪枝5:等效位置剪枝-开头失败不再试同长度开头*/ /*剪枝6:最优填充剪枝-正好填满却失败则回溯*/ if(len==0||len+i==sticklen)//排除等效冗余 { break; } } } } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n; for(int i=1;i<=n;i++) { cin>>a; sumlen+=a; maxlen=max(maxlen,a); minlen=min(minlen,a); b[a]++; } /*剪枝7:合法性剪枝-原棍长度必须是总长度的约数*/ /*剪枝8:枚举优化-长度从最长段开始枚举,确保最小可能长度*/ for(int l=maxlen;l<=sumlen;l++) { if(sumlen%l==0) { dfs(n,0,l,maxlen); if(hasans) { cout<<l; return 0; } } } return 0; }运行结果

感谢阅读,我们下期再会。