题目链接是洛谷的,有中文翻译的。
题目链接
题意:给你一个n,你要求出一个最短的单调递增数列,满足
a1=1
a
1
=
1
,
am=n
a
m
=
n
,输出任意一种
m
m
最小时a数组的方案。n<=10000
题解:
这题乍一看,暴力就是搜索啊,但是我随便想了三个很显然的剪枝之后,发现连样例都搜不出来。后来看了题解,发现要用到迭代加深搜索。我太弱了不会这种东西,便上网上现学的,有些理解不一定对,还望各位斧正。
迭代加深搜索的思路是,我们的搜索时间是有限的,那么我们就限制搜索的深度,看在限制的深度内是否能搜出解,所以从小到大枚举深度限制,如果时间还够,那么就搜深度限制更深的情况,否则结束搜索。
网上有人作了个很好的比喻,这个原理好比是下国际象棋,你在一开始棋子比较多的话你不需要一直算完所有可能的走法最终的输赢, 你只需要算出一个接下来几步比较合理的走法就可以;而残局的时候你可要算几十步的胜负来决定怎么走,一开始刚开局时计算量太大,无法完成,但是残局棋子不多的时候就可以并且更有必要算更多的步数了。假如你在开局时非要一直算出最后的输赢,可能你的程序就会运行非常长的时间,没法顺利地下棋了。
而这题如果我们从小到大枚举深度限制,只要出现了合法情况,那么当前是a数组就是答案(有SPJ),这样就让搜索时间大大减少了。
几个剪枝:
1.一旦搜到答案直接不断return,退出搜索。
2.计算用当前数往下搜的话,以后都用结果最大的合并方式,到了最后
会不会还是不够n。显然增长最快的合并方式是,于是我们只需要判断
ai∗2m−i
a
i
∗
2
m
−
i
和
n
n
<script id="MathJax-Element-1203" type="math/tex">n</script>的大小关系即可。
3.枚举的时候倒叙枚举已经在当前数组中的数,因为那样能更快地得到比较大的数,更可能搜出答案。
最后提醒一下,UVA是不忽略行末空格的,这个比较坑人。
代码:
c++
#include <bits/stdc++.h>
using namespace std;
int n,ans,a[10100],b[10100],pd,ksm[50];
void dfs(int x,int num,int lim)
{
if(x==n)
{
pd=1;
return;
}
if(num-1>=lim)
return;
for(int i=num-1;i>=1;--i)
{
for(int j=num-1;j>=1;--j)
{
if(a[i]+a[j]<=n&&ksm[lim-num]*(a[i]+a[j])>=n)
{
a[num]=a[i]+a[j];
dfs(a[num],num+1,lim);
if(pd==1)
return;
a[num]=0;
}
}
}
}
int main()
{
ksm[0]=1;
for(int i=1;i<=30;++i)
ksm[i]=ksm[i-1]*2;
while(~scanf("%d",&n))
{
if(n==0)
break;
ans=2e9;
pd=0;
if(n==1)
{
printf("1\n");
continue;
}
a[1]=1;
for(int i=2;i<=n;++i)
{
dfs(1,2,i);
if(pd==1)
{
for(int j=1;j<i;++j)
printf("%d ",a[j]);
printf("%d\n",a[i]);
break;
}
}
}
return 0;
}