埃及分數問題-IDDFS

本文轉自http://blog.csdn.net/u014800748/article/details/47376435

迭代加深搜索(IDDFS)的思想

迭代加深搜索一般用來求解狀態樹“非常深”,甚至深度可能趨於無窮,但是“目標狀態淺”的問題。如果用普通的DFS去求解,往往效率不夠高。此時我們可以對DFS進行一些改進。最直觀的一種辦法是增加一個搜索的最大深度限制maxd,一般是從1開始。每次搜索都要在maxd深度之內進行,如果沒有找到解,就繼續增大maxd,直到成功找到解,然後break。


如下圖所示,如果用DFS,需要15步才能找到結點3,但是用迭代加深搜索,很快即可找到結點3。

迭代加深搜索

在使用迭代加深搜索時,通常還要引入一個估價函數h(),來預測從當前深度還有至少多少步才能到達目標狀態。假設當前在第cur層,當cur+h(cur)>maxd時候,就說明不論怎麼走,都不可能在maxd的限制之內找到目標狀態,此時就可以進行“剪枝”操作。這樣的和帶有估價函數的迭代加深搜索就是IDA*算法。爲了更好的理解該算法,下面以“埃及分數”這一經典的例子來作爲說明。


埃及分數問題

在古埃及,人們使用單位分數的和(即1/a,a是自然數)來表示一切有理數,例如,2/3=1/2+1/6,但是不允許2/3=1/3+1/3,因爲在加數中不允許有相同的。對於一個分數a/b,表示的方法有很多種,其中加數少的比加數多的好,如果加數個數相同,那麼最小的分數越大越好。例如,19/45=1/5+1/6+1/18是最優方案。

輸入兩個整數a,b(0<a<b<500),試編程計算最佳表達式。

樣例輸入:

495 499

樣例輸出:

Case 1: 495/499=1/2+1/5+1/6+1/8+1/3992+1/14970


問題分析

首先,根據題意可以發現,本題的解答樹非常的龐大,不僅深度沒有明顯的上界,而且在理論上加數的選擇也是無限的,即每一層的結點數量也是無限的。這樣的話,如果使用普通的BFS,第一層都擴展不完。然而本題要實現兩個目標:1.加數的個數儘量少。2.最小的那個分數儘量大,即最大的分母儘量小。


爲了實現第一個目標,我們自然想到可以逐一枚舉可能的個數,設maxd表示一共有maxd+1個分數相加恰好等於a/b(下標從0開始),按照這樣的思路,一定可以找到加數最少的解。


接下來考慮如何實現第二個目標。第二個目標其實是在告訴我們如果存在多解的時候,要更新當前找到的最優解。這裏容易犯的一個錯誤是:找到了解之後立即全部return true。這樣的寫法只能實現目標1,並沒有真正實現目標2。


那麼自然會問:那正確的做法是什麼?正確的做法應該是找到了一組解後,繼續返回上一層繼續新的搜索。假設當前已經在第cur層,即0~cur的所有的分母都已經確定了,剩下的分數還需要aa/bb,再假設我們此時需要從分母i>=from的數開始尋找,不難發現,如果(maxd+1-d)*(1/i)≤aa/bb,即剩下的分數全部都爲1/i時候,他們的和纔剛好等於aa/bb。而實際情況是:分母不允許重複出現,即實際的和肯定小於(maxd+1-d)*(1/i),這個時候如果i繼續增加,(maxd+1-d)*(1/i)只會更小於aa/bb,情況更糟糕,因此在此處應該停止枚舉。這樣,我們就分析出來了正確的停止搜索的條件:當出現(maxd+1-d)*(1/i)≤aa/bb時,break。


通過以上分析,我們還確定出來了dfs時候的入口參數: cur表示當前在第cur層,from表示第cur層分母的起點, aa表示剩下的分數的分子,bb表示剩下的分數的分母。即dfs(cur,from,aa,bb)。至此,本題的算法的整體框架已經分析完畢了,我們成功地利用IDA*算法解決了這道經典例題。


代碼:

  1. #define _CRT_SECURE_NO_WARNINGS  
  2. #include<iostream>  
  3. #include<algorithm>  
  4. #include<string>  
  5. #include<sstream>  
  6. #include<set>  
  7. #include<vector>  
  8. #include<stack>  
  9. #include<map>  
  10. #include<queue>  
  11. #include<deque>  
  12. #include<cstdlib>  
  13. #include<cstdio>  
  14. #include<cstring>  
  15. #include<cmath>  
  16. #include<ctime>  
  17. #include<cctype>  
  18. #include<functional>  
  19. using namespace std;  
  20.   
  21. #define me(s) memset(s,0,sizeof(s))  
  22. #define pb push_back  
  23. typedef long long ll;  
  24. typedef unsigned int uint;  
  25. typedef unsigned long long ull;  
  26. typedef pair <intint> P;  
  27.   
  28.   
  29.   
  30. int maxd;  
  31. const int N = 1000;  
  32. int ans[N];  
  33. int v[N];  
  34.   
  35. int get_first(int x, int y)//找到第一個小於x/y的單位分數的分母  
  36. {  
  37.     int res = y / x;  
  38.     return res*x >= y ? res : res + 1;  
  39. }  
  40.   
  41. ll gcd(ll a, ll b)  
  42. {  
  43.     return b == 0 ? a : gcd(b, a%b);  
  44. }  
  45.   
  46. bool better(int d)//更新當前的解  
  47. {  
  48.     for (int i = d; i >= 0; i--)//由於分母是由小到大存儲的,因此逆序枚舉  
  49.     if (v[i] != ans[i])  
  50.         return ans[i] == -1 || v[i]<ans[i];  
  51.     return false;  
  52. }  
  53. bool dfs(int d, int from, ll aa, ll bb)  
  54. {  
  55.     if (d == maxd)  
  56.     {  
  57.         if (bb%aa)return false;//如果不能整除,搜索失敗  
  58.         v[d] = bb / aa;        //不要忽略最後一個分母  
  59.         if (better(d))memcpy(ans, v, sizeof(ll)*(d + 1));  
  60.         return true;  
  61.     }  
  62.     bool ok = false;  
  63.     from = max(from, get_first(aa, bb));//更新from這一步容易忽略,假設第d-1個分數的分母是a,第d個分數的分母不一定要從a+1開始,還要考慮1/(a+1)是否小於等於aa/bb  
  64.     for (int i = from;; i++)  
  65.     {  
  66.         if (bb*(maxd + 1 - d) <= i*aa)break;//此處纔是正確的停止枚舉的條件  
  67.         v[d] = i;//枚舉新的分母  
  68.         ll b2 = bb*i;//計算aa/bb-1/i的分子,分母  
  69.         ll a2 = aa*i - bb;  
  70.         ll g = gcd(a2, b2);//計算最大公約數,進行約分  
  71.         if (dfs(d + 1, i + 1, a2 / g, b2 / g))ok = true;//注意,不要寫成return true,因爲找到一組解並不能當做停止枚舉的條件  
  72.     }  
  73.     return ok;//返回從第cur層到maxd層是否成功找到了解,ok一旦爲true就一直是true  
  74. }  
  75. int main()  
  76. {  
  77.     int a, b;  
  78.     int rnd = 0;  
  79.     while (~scanf("%d%d", &a, &b))  
  80.     {  
  81.         int ok = 0;  
  82.         for (maxd = 1;; maxd++)//迭代加深搜索的主體框架,maxd要設置爲全局變量  
  83.         {  
  84.             memset(ans, -1, sizeof(ans));//開始新一輪搜索時不完整的答案要清空  
  85.             if (dfs(0, get_first(a, b), a, b))  
  86.             {  
  87.                 ok = 1;  
  88.                 break;  
  89.             }  
  90.         }  
  91.         printf("Case %d: %d/%d=", ++rnd, a, b);  
  92.         for (int i = 0; i <= maxd; i++)  
  93.         {  
  94.             if (i)printf("+");  
  95.             printf("1/%d", ans[i]);  
  96.         }  
  97.         printf("\n");  
  98.     }  
  99. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章