埃及分數

descriptionios

         對於每個非負有理數,咱們知道它必定能劃歸成某些特殊真分數之和,特殊真分數要知足它們的分子爲1,可是咱們知道,對於無窮級數1/2+1/3+1/4…。雖然,它是發散的,可是改級數增加得極爲緩慢,例如到了數百萬以後,和也在18~19左右。數組

         若干年來,不斷有人宣稱發現了該級數的特殊性質,這些都對這個問題的研究起到了深遠的影響。框架

         你的任務來了,要求給你個真分數,你須要將其化簡爲最少的若干特殊真分數之和,你要輸出這個序列(序列按遞增序)。spa

         若是有不一樣的方案,則分數個數相同的狀況下使最大的分母最小。若還相同,則使次大的分母最大……以此類推。code

         如:2/3=1/2+1/6,但不容許2/3=1/3+1/3,由於加數中有相同的。blog

         對於一個分數a/b,表示方法有不少種,可是哪一種最好呢?ip

         首先,加數少的比加數多的好,其次,加數個數相同的,最小的分數越大越好。如:ci

         19/45=1/3 + 1/12 + 1/180get

         19/45=1/3 + 1/15 + 1/45string

         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*)能夠解決上面的問題。一方面咱們要解決,利用DFS從小到大枚舉深度上限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;
      }  
}

code:

 

#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;
}

 

 

方法:迭代加深解答樹並進行剪枝
剪枝點:

    1. 要表示的分數與已經分配到分數總和的差值要大於0才繼續嘗試
    2. 根據剩餘深度和上面的差值肯定每層嘗試分數分母的上限,下限即前一分配好的分數的分母加1
      #include <iostream>  
      #include <math.h>  
      using namespace std;  
      #define N 10000000  
      int array[N];  
        
      double is_equal(int a, int b, int cur)  
      {  
          double sum = 0;  
          for (int i = 0; i <= cur; i++)  
              sum += (double)1 / array[i];  
          return (double)a / b - sum;  
      }  
        
      int dfs(int cur, int d, int a, int b)  
      {  
          if (cur == d)  
              return 0;  
          int start = (int)(d - cur) / (is_equal(a, b, cur - 1));  
          int end = cur == 0 ? 2 : array[cur-1] + 1;  
          for (int i = start;i>=end; i--)  
          {  
              array[cur] = i;  
              double p = is_equal(a, b, cur);  
              if (abs(p) <= 0.00001)  
              {  
                  for (int j = 0; j <= cur; j++)  
                      cout << array[j] << ' ';  
                  cout << endl;  
                  return 1;  
              }  
              else if (p > 0)  
              {  
                  if (dfs(cur + 1, d, a, b))  
                      return 1;  
              }  
          }  
          return 0;  
      }  
        
      int main()  
      {  
          int a, b;  
          cin >> a >> b;  
          for (int i = 1; ; i++)  
          {  
              //cout << i << endl;  
              if (dfs(0, i, a, b))  
                  break;  
          }  
      }  
相關文章
相關標籤/搜索