zoukankan      html  css  js  c++  java
  • 埃及分数

    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*)可以解决上面的问题。一方面我们要解决,利用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
    ",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;  
            }  
        }  
  • 相关阅读:
    3.2spring源码系列----循环依赖源码分析
    3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
    Jetson AGX Xavier ROS 调用usb单目摄像头运行ORB_SLAM2
    Jetson AGX Xavier ROS下调用USB单目摄像头
    SpringCloud-OpenFeign组件的使用
    SpringCloud-服务间通信方式
    SpringCloud-服务注册中心
    SpringCloud入门
    K8s—集群搭建
    Redis—过期策略以及内存淘汰机制
  • 原文地址:https://www.cnblogs.com/nbalive2001/p/6845555.html
Copyright © 2011-2022 走看看