description |
對於每一個非負有理數,我們知道它一定能划歸成某些特殊真分數之和,特殊真分數要滿足它們的分子為1,但是我們知道,對於無窮級數1/2+1/3+1/4…。雖然,它是發散的,但是改級數增長得極為緩慢,例如到了數百萬之后,和也在18~19左右。 若干年來,不斷有人宣稱發現了該級數的特殊性質,這些都對這個問題的研究起到了深遠的影響。 你的任務來了,要求給你個真分數,你需要將其化簡為最少的若干特殊真分數之和,你要輸出這個序列(序列按遞增序)。 如果有不同的方案,則分數個數相同的情況下使最大的分母最小。若還相同,則使次大的分母最大……以此類推。 如:2/3=1/2+1/6,但不允許2/3=1/3+1/3,因為加數中有相同的。 對於一個分數a/b,表示方法有很多種,但是哪種最好呢? 首先,加數少的比加數多的好,其次,加數個數相同的,最小的分數越大越好。如: 19/45=1/3 + 1/12 + 1/180 19/45=1/3 + 1/15 + 1/45 19/45=1/3 + 1/18 + 1/30 19/45=1/4 + 1/6 + 1/180 19/45=1/5 + 1/6 + 1/18 最好的是最后一種,因為18 比180, 45, 30,都小。 |
對於此類搜索問題,搜索深度沒有明顯的上界,而且加數的選擇在理論上也是無限的,也就是說寬度搜索連一層都擴展不完。
利用迭代加深搜索(IDA*)可以解決上面的問題。一方面我們要解決,利用BFS從小到大枚舉深度上限maxd,雖然理論上深度是無限的,但是只要保證有解,深度值必然在有限的時間內能枚舉到。
另一方面,我們還要解決每次枚舉的值無限的問題,可以借助maxd來“剪枝”,以此題為例,每一次枚舉的的分母個數必然有一個結束值,否則就會出現無限下去的尷尬了。那么當擴展到第i層時,第i層分數值為1/e,那么接下來由於分母是以遞增順序進行的,那么分數值都不會大於1/e,如果c/d(前i個分數之和)+1/e*(maxd-i)<a/b,可以直接break;因為起碼深度不夠,要再加深度。
基本框架:
for(maxd=1;;maxd++) { if(dfs(0,,,)) { ok=1; break; } }
代碼如下:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define MAXN 20000 //一定要用64位的整數,由於里面存在化歸為同分母的操作,需要相乘 using namespace std; int64_t maxd; int64_t v[MAXN],ans[MAXN];//v是暫時存放的滿足題意的分母的數組,ans是記錄滿足題意的最優分母值的數組。 int64_t gcd(int64_t a,int64_t b)//求最大公約數 { int64_t m; while(a!=0) { m=b%a; b=a; a=m; } return b; } int64_t getfirst(int64_t a,int64_t b)//取比a/b小的最大分數,分子必須為1 { int64_t i,j; for(i=2;;i++) { if(b<a*i) { break; } } return i; } bool better(int64_t d)//必要的判斷,這組分數之和是否滿足最優解 { int64_t i; bool flag=true; if(ans[d]==-1)//由於初始化ans是-1,如果是第一次出現的滿足題意的解,返回true return true; for(i=d;i>=0;i--) { if(v[i]==ans[i])//從高位進行判斷大小,題意要求 continue; else if(v[i]>ans[i]) { //return false; flag=false; break; } else { break; } } if(flag==true) return true; else return false; } bool dfs(int64_t a,int64_t b,int64_t from,int64_t d)//深度為d { int64_t aa,bb,g,i; bool ok; // cout<<from<<endl; if(d==maxd) { if(a!=1) return false; for(i=0;i<=d-1;i++) { if(v[i]==b) { return false; } } v[d]=b; sort(v,v+d); if(better(d)) memcpy(ans,v,sizeof(int64_t)*(d+1)); return true; } ok=false; //重要!!! from=max(from,getfirst(a,b));//枚舉起點,去上一次加一的分母值和比a/b小的最大分數的分母中更大的。 for(i=from;;i++) { //剪枝,如果c/d(前i個分數之和)+1/e*(maxd-i)<a/b,可以直接break; if(b*(maxd+1-d)<=a*i) break; v[d]=i; aa=a*i-b; bb=b*i; g=gcd(aa,bb); aa=aa/g; bb=bb/g;//約分 if(dfs(aa,bb,i+1,d+1)) ok=true; } return ok; } int main() { int64_t a,b,aa,bb,i,g; while(cin>>a>>b) { if(a==0) { cout<<a<<"/"<<b<<"=0"<<endl; continue; } memset(ans,-1,sizeof(ans)); g=gcd(a,b); aa=a/g; bb=b/g; if(aa==1) { printf("%d/%d=%d/%d\n",a,b,aa,bb); } else { for(maxd=1;;maxd++) { if(dfs(aa,bb,getfirst(aa,bb),0)) break; } cout<<a<<"/"<<b<<"="; for(i=0;i<=maxd-1;i++) cout<<"1/"<<ans[i]<<"+"; cout<<"1/"<<ans[i]; cout<<endl; } } return 0; }