1. 程式人生 > >埃及分數問題 【IDA*】

埃及分數問題 【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

分析

本題可以用dfs回溯來求解。但是由於本題沒有指明等式數目即深度,如果dfs搜尋的話是沒有上限的,換句話說,如果用寬度優先遍歷,連一層都擴充套件不完(因為每一層都是無限大的)。所以需要列舉深度,直到找到跳出即可,即迭代深度搜索。
深度上限maxd還可以用來“剪枝”。 按照分母遞增的順序來進行擴充套件,如果擴充套件到i層時,前i個分數之和為c/d,而第i個分數為1/e,則接下來至少還需要(a/b-c/d)/(1/e)個分數,總和才能達到a/b。 例如,當前搜尋到19/45=1/5+1/100+…,則後面的分數每個最大為1/101,至少需要(19/45-1/5) / (1/101) =23項總和才能達到19/45,因此前22次迭代是根本不會考慮這棵子樹的。 這裡的關鍵在於:可以估計至少還要多少步才能出解。
注意,這裡的估計都是樂觀的,因為用了“至少”這個詞。 說得學術一點,設深度上限為maxd,當前結點n的深度為g(n),樂觀估價函式為h(n),則當g(n)+h(n)>maxd時應該剪枝。 這樣的演算法就是IDA*。 當然,在實戰中不需要嚴格地在程式碼裡寫出g(n)和h(n),只需要像剛才
那樣設計出樂觀估價函式,想清楚在什麼情況下不可能在當前的深度限制下出解即可。

思路

(1)列舉深度maxd
(2)dfs搜尋:從滿足1/c<=a/b的最小c即b/a+1開始列舉分母,深度d為表示第幾個分數;每次計算的是a/b - 1/i = a2/b2 然後將a2,b2再作為a,b進行遞迴。
(3)剪枝:if(bb * (maxd+1-d) <= i*aa) break;//剪枝:如果剩下的maxd+1-d個分數全部都是1/i,加起來仍然不超過aa/bb,則無解! (maxd+1-d)/i <= aa/bb

參考

入門經典-埃及分數-P 

程式碼

#include <iostream>
#include <cstdio>
#include <cstring> using namespace std; typedef long long LL; const int maxn = 1000; LL ans[maxn],v[maxn]; int maxd,kcase; int gcd(LL a,LL b){//最大公約數,用於約分 LL temp; while(b){temp = b;b = a % b;a = temp;} return a; } LL get_first(LL a, LL b){//滿足1/c<=a/b的最小c return b/a + 1; } bool better(int
d){//更新最優值:如果當前解v比目前最優解ans更優,更新ans for(int i=d;i>=0;i--) if(v[i] != ans[i]) return ans[i] == -1 || v[i] < ans[i]; return false; } //當前深度為d,分母不能小於from,分數之和恰好為aa/bb bool dfs(int d,int from,LL aa,LL bb){ if(d == maxd){//深度達到當前列舉個數 if(bb % aa) return false;//aa/bb必須是埃及分數 v[d] = bb/aa;//儲存分母 if(better(d)) memcpy(ans,v,sizeof(LL)*(d+1));//更新最優解 return true;//返回成功 } bool ok = false;//用於返回本次遞迴的結果 from = max((LL)from,get_first(aa,bb));//列舉的起點 for(int i=from; ;i++){ if(bb * (maxd+1-d) <= i*aa) break;//剪枝:如果剩下的maxd+1-d個分數全部都是1/i,加起來仍然不超過aa/bb,則無解! (maxd+1-d)/i <= aa/bb v[d] = i;//儲存分母 //計算aa/bb - 1/i,設結果為a2/b2 LL b2 = bb*i; LL a2 = aa*i - bb; LL g = gcd(a2,b2);//用於約分 if(dfs(d+1,i+1,a2/g,b2/g)) ok = true;//找到解返回成功 } return ok;} void solve(int a,int b){ int ok = 0; for(maxd = 1; ;maxd++){//列舉深度,即等式相加的個數 memset(ans,-1,sizeof(ans)); if(dfs(0,get_first(a,b),a,b)){//遞迴找到時返回成功 ok = 1;break;//標記並退出列舉 } } printf("Case %d: %d/%d=",++kcase,a,b); if(ok) {for(int i=0;i<maxd;i++) printf("1/%lld+",ans[i]);printf("1/%lld\n",ans[maxd]);} else printf("%d/%d\n",a,b); } int main() { int a,b; while(scanf("%d%d",&a,&b)!=EOF) solve(a,b); return 0; }