課余時間仔細看了下教練給我們發的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 }