课余时间仔细看了下教练给我们发的OI技能树,发现自己似乎连迭代加深搜索都不会打,看来自己大抵的确是变水了 _(:зゝ∠)_。惭愧,惭愧,好在现在学习为时不晚,还可以补救。
学习算法时,一个关键的问题是什么时候来使用它。在一些搜索问题中,使用普通的DFS可能会让你把时间浪费在深度非常大而且答案不是最优的搜索过程上,甚至有的时候DFS搜索的深度是无穷的,而BFS虽说理论上可以避免这种情况,却又无法满足题目的某些需求,或者无法实现。仔细思考一下这个例子,它有着两个特征:一是它是个最优解问题,最优的答案深度最小;二是它搜索时可能会达到极大的深度,而这样的答案是没用且费时的。
既然这个算法的名字叫做迭代加深搜索,那么它的核心之处就是“迭代加深”了。什么是迭代加深呢?正如上面说的,某些问题搜索时可能会存在搜索很深却得不到最优解的情况,那么我们就给搜索设置一个约束,当搜索深度达到约束值却还没找到可行解时结束搜索。如果我们在一个深度约束下没有搜索到答案,那么答案一定在更深的位置,那么就把约束深度调整到更深,然后再次搜索,直到搜索到答案为止。
但是这是个最优解问题,我们怎么能够保证我们搜索到的答案是最优的呢?其实这很好证明,如果答案是在深度为h的时候搜索到的,说明深度约束一直增加到了h,也就是说,在深度约束更小的时候,我们没有找到有效的答案,也就是说我们并没有找到深度更小的答案(也就是更优的答案),那么这个答案必然是最优,也是深度最小的。
在实际运用中,如果没有一个合适的方法来剪枝,迭代加深搜索也会很容易超时。好在迭代加深搜索有一个比较特殊的剪枝方法,就是对当前的情况通过一个乐观估计函数进行预估,如果发现即使在最好的情况下搜索到当前的最深深度限制也没办法得到答案,那么就及时退出来实现剪枝。
当然,注意一下,使用迭代加深搜索的时候,一定要确定这个问题是有解的,否则会陷入不断加深的死循环。
下面给出两个IDDFS的基础题,相信能在理解上起到一定的帮助:
POJ 3134 - Power Calculus
链接:http://poj.org/problem?id=3134
题目大意是给定一个正整数n,求经过多少次乘法或除法运算可以从x得到xn,可以使用中间得到的结果。
这个题目显然是有解的(连续乘n次x总是能得到xn的,只是可能不是最优解)并且操作次数最小时有搜索深度最小,而理论上搜索深度可以是无穷的,这种情况下就可以使用IDDFS了。
代码如下:
1 #include <iostream> 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <sstream> 7 #include <algorithm> 8 #include <cmath> 9 #include <vector> 10 #include <stack> 11 #include <queue> 12 #include <list> 13 #define the_best_pony "Rainbow Dash" 14 15 using namespace std; 16 17 int n,maxh; 18 int a[1010]; 19 20 bool dfs(int x,int cur){ 21 if(x<<(maxh-cur)<n) return false; //乐观估计剪枝,当前深度到限制深度指数最多增长2^(maxh-cur)倍 22 if(cur>maxh) return false; //达到限制深度 23 a[cur]=x; 24 if(x==n) return true; 25 for(int i=0;i<=cur;i++){ 26 if(dfs(x+a[i],cur+1)) return true; 27 if(dfs(x>a[i]?x-a[i]:a[i]-x,cur+1)) return true; 28 } 29 return false; 30 } 31 32 int main(){ 33 while(scanf("%d",&n)&&n){ 34 maxh=0; 35 memset(a,0,sizeof(a)); 36 while(!dfs(1,0)){ //只要没找到答案就一直继续 37 memset(a,0,sizeof(a)); 38 maxh++; //增大深度限制 39 } 40 printf("%d\n",maxh); //最大深度就是答案 41 } 42 return 0; 43 }
POJ 2286 - The Rotation Game
链接:http://poj.org/problem?id=2286
题目大概意思是通过最少的操作,将下面的图形的中间八个数字变成一样的。如下图所示:
我知道我描述得很糟糕,建议还是阅读原题面来理解一下,这玩意儿描述起来很麻烦的。
这个题目最为麻烦的地方在于各种操作,不过只要把表打好,处理好各种细节问题之后大体还是比较简单的。它的估计函数是通过统计最少差异来实现的。
代码:
1 #include <iostream> 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <string> 6 #include <sstream> 7 #include <algorithm> 8 #include <cmath> 9 #include <vector> 10 #include <stack> 11 #include <queue> 12 #include <list> 13 #define the_best_pony "Rainbow Dash" 14 15 using namespace std; 16 17 int rot[10][10]={ //操作打表 18 {0,2,6,11,15,20,22}, //A 19 {1,3,8,12,17,21,23}, //B 20 {10,9,8,7,6,5,4}, //C 21 {19,18,17,16,15,14,13}, //D 22 {23,21,17,12,8,3,1}, //E 23 {22,20,15,11,6,2,0}, //F 24 {13,14,15,16,17,18,19}, //G 25 {4,5,6,7,8,9,10} //H 26 }; 27 int cent[10]={6,7,8,11,12,15,16,17}; //中间8个位置 28 int a[30],maxh=1; 29 char put[100001]; 30 31 bool check(){ //判断中间是否一样 32 for(int i=0;i<8;i++) 33 if(a[cent[0]]!=a[cent[i]]) return false; 34 return true; 35 } 36 37 int cnt(){ 38 int num=~0u>>1; 39 for(int i=1;i<=3;i++){ 40 int tmp=0; 41 for(int j=0;j<8;j++) 42 if(a[cent[j]]!=i) tmp++; 43 num=min(num,tmp); 44 } 45 return num; 46 } 47 48 void move(int x){ //操作 49 int tmp=a[rot[x][0]]; 50 for(int i=0;i<6;i++) a[rot[x][i]]=a[rot[x][i+1]]; 51 a[rot[x][6]]=tmp; 52 return; 53 } 54 55 bool dfs(int dep){ 56 if(dep>maxh) return false; 57 if(check()){ 58 put[dep]='\0'; 59 return true; 60 } 61 if(dep+cnt()>maxh) return false; //乐观估计剪枝 62 for(int i=0;i<8;i++){ 63 put[dep]='A'+i; 64 move(i); 65 if(dfs(dep+1)) return true; 66 if(i%2==0) move((i+5)%8); //反向操作 67 else move((i+3)%8); //反向操作 68 } 69 return false; 70 } 71 72 int main(){ 73 while(scanf("%d",&a[0])&&a[0]){ 74 maxh=1; 75 for(int i=1;i<24;i++) scanf("%d",&a[i]); 76 if(check()) printf("No moves needed\n%d\n",a[cent[0]]); 77 else{ 78 while(!dfs(0)) maxh++; 79 printf("%s\n%d\n",put,a[cent[0]]); 80 } 81 } 82 return 0; 83 }