麻了,整了一下午的送礼物,人都废了
写篇水贴,休息一下吧
4. 迭代加深
迭代加深,简写为iddfs,是一种特殊的dfs,其特殊在于每一次搜索都限定了搜索树的深度
这有什么用呢?
对于某些题而言,如果使用普通的dfs,可能会因为该搜索树无穷大而T飞;如果使用普通的bfs,空间可能会炸飞,而且每一个阶段的状态难以定义(比如,如果使用普通的bfs完成骑士精神,你打算怎么定义其状态)
而iddfs,则是用dfs的形式完成bfs的功能,空间复杂度与dfs类似,时间复杂度与bfs类似,非常的优秀
从理论上来讲,iddfs和dfs的写法几乎一样,只是多了一个限定的深度的判断
比如:
//iddfs内部:
if(x>depth){//如果已经超出了当前深度,直接退出
return ;
}
//主函数
iddfs(x);//迭代加深搜索
if(flag){//找到了答案
//输出答案
}
depth++;//放大限制的层数
所以,直接上例题罢
4.1. 实战演练
一句话题意:
现有一长度为 m m m 的数列 a a a,且数列 a a a 有如下性质: a 1 = 1 , a m = n , ∀ k 满足 a k = a i + a j ( 0 ≤ i , j ≤ k − 1 , i , j 可以相等 ) a_1=1,a_m=n,\forall\ k\ \text{满足}\ a_k=a_i+a_j(0\le i,j\le k-1,i,j\ \text{可以相等}) a1=1,am=n,∀ k 满足 ak=ai+aj(0≤i,j≤k−1,i,j 可以相等)
给定 n n n,求在 m m m 最小的情况下,字典序最大的 a a a 数列
显然,由于无法确定 m m m,所以普通的dfs显然T飞
因此,我们可以使用迭代加深,限制 m m m 的大小,这样,我们也就保证了最先搜出来的数列一定保证 m m m 最小
然后,就是常规的搜索
注意几个剪枝点:
- 由数列 a a a 的性质得,该数列一定是递增的,所以可以根据这一点来进行剪枝
- 如果新得到的数大于 n n n,也可以直接停止了
其实都是很显然的
另外,为了构造字典序最大的序列,我们可以使每一次的 a i , a j a_i,a_j ai,aj 在合法情况下取尽可能大的值,以构造最大的字典序
因此,我们很快就能愉快的打出代码:
#include<cstdio>
int n,depth;
bool flag;
int now[25],ans[25];
void dfs(int x){
if(x>depth){
return ;
}
for(int i=x-1;i>=1;i--){
for(int j=i;j>=1;j--){//枚举加数a_i,a_j,得到a_k
if(now[i]+now[j]>n){//通过剪枝2剪枝
continue;
}
if(now[i]+now[j]<now[x-1]){//通过剪枝1剪枝
break;
}
now[x]=now[i]+now[j];
if(now[x]==n){//已经得到了答案
for(int i=1;i<=depth;i++){//为输出做准备
ans[i]=now[i];
}
flag=1;//标记,已经有了答案
return ;
}
dfs(x+1);
if(flag){//如果我们已经找到了答案,直接退出
return ;
}
}
}
}
int main(){
now[1]=ans[1]=1,now[2]=ans[2]=2;//初始化,用于特判
while(1){
scanf("%d",&n);
if(!n){
return 0;
}
if(n==1){//1,2特判,但是2不特判好像也行?
printf("1\n");
continue;
}else if(n==2){
printf("1 2\n");
continue;
}
for(depth=3;depth<=20;depth++){//迭代加深
dfs(3);
if(flag){//找到答案就输出
for(int i=1;i<=depth;i++){
printf("%d ",ans[i]);
}
printf("\n");
break;
}
}
flag=0;//初始化
for(int i=3;i<=depth;i++){
now[i]=ans[i]=0;
}
}
return 0;
}
但是,很可惜,还是T了
其实,这里还有一个至关重要的优化:
假设当前我们已经构造了 a k a_k ak,限制的长度为 m 1 m_1 m1
思考:如果我们不考虑合法性,怎样使 a k + 1 a_{k+1} ak+1 最大化?
显然,因为 a k a_k ak 使当前序列里的最大值,所以, max { a k + 1 } = 2 × a k \max\{a_{k+1}\}=2\times a_k max{ak+1}=2×ak
同理, max { a k + 2 } = 2 × a k + 1 \max\{a_{k+2}\}=2\times a_{k+1} max{ak+2}=2×ak+1
根据这样的规律,我们就可以得出在当前的 a k a_k ak 下, a m 1 a_{m_1} am1 的最大值
如果 a m 1 < n a_{m_1}<n am1<n,说明什么?
说明在当前情况下,即使每一步都选择最优方法,都不能构造出一组可行解
既然如此,我们就可以提前去减掉 a k a_k ak,以避免时间的浪费
如上的过程,我们可以写作一个估价函数,这里由于实现简单,故蒟蒻为写成函数
BTW,如果我们在普通的dfs中整一个估价函数,这个dfs就升级为A*,如果我们在iddfs中整一个估价函数,这个dfs就升级为IDA*
现在,我们就可以得到AC代码了
#include<cstdio>
int n,depth,now[25],ans[25],flag;
void dfs(int x){
if(flag){
return ;
}
if(x>depth){
return ;
}
for(int i=x-1;i>=1;i--){
for(int j=i;j>=1;j--){
if(now[i]+now[j]>n){
continue;
}
if(now[i]+now[j]<now[x-1]){
break;
}
int sum=now[i]+now[j];//上文所说的估价函数进行优化
for(int k=x+1;k<=depth;k++){
sum*=2;
}
if(sum<n){
break;
}
now[x]=now[i]+now[j];
if(now[x]==n){
for(int i=1;i<=depth;i++){
ans[i]=now[i];
}
flag=1;
return ;
}
dfs(x+1);
if(flag){
return ;
}
}
}
}
int main(){
now[1]=ans[1]=1,now[2]=ans[2]=2;
while(1){
scanf("%d",&n);
if(!n){
return 0;
}
if(n==1){
printf("1\n");
continue;
}else if(n==2){
printf("1 2\n");
continue;
}
for(depth=3;depth<=20;depth++){
dfs(3);
if(flag){
for(int i=1;i<=depth;i++){
printf("%d ",ans[i]);
}
putchar('\n');
break;
}
}
flag=0;
for(int i=3;i<=depth;i++){
now[i]=ans[i]=0;
}
}
return 0;
}