埃及分数(迭代加深搜索)

24 篇文章 0 订阅
24 篇文章 0 订阅
埃及分数问题,首先,这是该问题的链接:

http://acm.nefu.edu.cn/JudgeOnline/problemshow.php?problem_id=358

欢迎AC

题目大意:

对于每一个非负有理数,我们知道它一定能划归成某些特殊真分数之和,特殊真分数要满足它们的分子为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,都小。

输入:多组数据输入. 每组输入一行,a,b,(0<=a<b<=1000)表示要化简的数为a/b 输出:输出一个算式,格式见样例。每个+、=号两边都没空格。

期初拿到这道题之后的思路是DFS+剪枝,后来发现,剪枝的实现真实太难了,儿深度又如此之大,所以就百思不得其解了。

上网查阅了些资料后我才发现,这道题用了一种叫做迭代加深搜索的搜索方式。

下面本人从“科学哲学”的角度说明本题的全部解题过程。

首先,我们可以看出我们需要的结果是一个分母的k元组,而这k个数是不存在重复的,且是递增的,所以可以确定大方向,一定是DFS,但是深度又不确定。题目中说了从1/2加到1/1000000的值才不过十几,所以可知深度实在是可能很大。所以考虑对深度进行限制,这就是迭代加深搜索,其实本质上可以看成是BFS型的DFS

这样搜索方式就定了,我们可以对不符合当前搜索深度要求的所有情况进行剪枝,知道找到最优解为止。

接下来进行题目分析:

对于任意给定的a,b1/k<=a/b<=1/p成立(这比较抽象,不过幸好这不是重点),也就是说,为了使程序具有更高的剪枝效率,我们需要找到所谓的上界与下界。

对于上界的寻找,我们可以极端化考虑。对于任意满足条件的a,b,都有a/b<INF/b,所以我们规定上界为INF/b ,这个想法很单纯,切勿多想。

对于下界的寻找:a/b=1/(b/a),所以如果规定int t=b/a,那么t<=b/a恒成立,所以可以说1/t>=1/(b/a)恒成立。又在递归迭代搜索中,dep表示当前搜索限制深度,k表示当前搜索深度,则当前分数就一定是由(dep-k+1)个特殊真分数的和,每个真分数最大就是1/[(b/a)*(dep-k+1)],所以对原式进行放缩,有:

a/b=1/(b/a)=(dep-k+1)*1/[(b/a)*(dep-k+1)]<=1/t,即a/b>=t,故可知,t为理论下界。

这样上下界都有了,我们可以写出下列代码:

#include <iostream>

#include <cstring>

#include <cstdio>

using namespace std;

#define LL __int64;

const int INF = ~0U>>1;   

//无穷大的表示法(~0U的意思是先把0强制转化为unsigned类型,再各位取反),这样得到的就是最大值。

const int N = 1000;    //最大所需深度

int dep,flag;   //当前深度限制变量 成功标记变量

int ans[N],d[N];   //ans为结果数组,d为深度结果数组

//最大公约数算法

int gcd(int a,int b) {return b ?gcd(b,a%b):a;}

//DFS基本主模块

void dfs(int a,int b,int k)   //k为当前深度

{

   if(k == dep + 1) return;   //深度优先出口

if(b % a == 0&& b / a > d[k-1]) 

//这种情况是找到了解的情况,其实就是a==1的情况,这种情况肯定有解。

    {

       d[k] = b / a;

       if(!flag || d[k] < ans[k])

            memcpy(ans,d,sizeof(d));

       flag = 1;

       return;

    }

   int s = b / a;   //分母的理论下界寻找

   if(s <= d[k-1]) s = d[k-1] + 1;  //确保下界是正确的

   int t = (dep - k + 1) *  b /a;   //分母的理论上界寻找

   if(t > INF / b) t = INF / b;   //确保上界是正确的

   if(flag && t >= ans[dep]) t = ans[dep] - 1;  //如果上界超过前层递归的范围就把上界进行缩短

   for(int i=s;i<=t;i++)

    {

       d[k] = i;   //表示k这一层目前所到的最大深度

       int m = gcd(i*a - b,b*i);   //最大公约数

       dfs((i*a-b)/m,b*i/m,k+1);    //继续DFS

    }

}

void Work(int a,int b)

{

   d[0] = 1;   //d[0]初始化

   flag = 0;   //默认找不到解

   for(dep=1;dep <= N;dep++)   //深度加深

    {

       dfs(a,b,1);  //进行迭代递归搜索

       if(flag)   //如果找到解的话

       {

           printf("1/%d",ans[1]);  //输出解

           for(int i=2;i<=dep;i++)

               printf("+1/%d",ans[i]);

           cout<<endl;

           break;  //不继续进行加深搜索

       }

    }

}

//主函数,不解释。

int main()

{

   int a,b;

   while(cin>>a>>b)

    {

       cout<<a<<"/"<<b<<"=";

       Work(a,b);

    }

   return 0;

}

至此,解题报告结束。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值