埃及分数问题 IDA*

14 篇文章 0 订阅
6 篇文章 0 订阅

  题意

在古埃及,人们使用单位分数的和(即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。

分析

本题可以用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,则无解

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<sstream>
#include<map>
#include<stack>
using namespace std;
typedef long long LL;
const LL BIG = 1000;
LL ans[BIG],now[BIG],maxd;
LL find_best(LL a,LL b){ //满足1/c<=a/b的最小c
    return b/a+1;
}
LL gcd(LL a,LL b){
    return b == 0 ? a : gcd(b, a%b); //求最大公约数
}
bool better(LL depth){ //更新最优值,如果当前解now比之前最优解ans更优,则更新ans
    for(LL d = depth;d>=0;d--)
        if(now[d]!=ans[d]) return ans[d]==-1||now[d]<ans[d];//(1)此时的深度尚未找到解(2)当前的分母小于之前解的分母,说明当前深度为d的分数比之前的分数大,则可以替换。否则不替换
    return false;
}
//当前深度为deep,接下来的分母不能小于next,接下来的分式之和恰好为aa/bb
bool dfs(LL deep,LL next,LL aa,LL bb){
    bool ok;
    if(deep==maxd){//此时到达了最后一层
        if(bb%aa) return false; //bb不能整除aa(aa!=1),构成不了埃及分式
        now[deep] = bb/aa;//取分母
        if(better(deep)) memcpy(ans, now, sizeof(long long)*(deep+1));//找到了更优的解,更新ans
        return true;
    }
    ok = false;
    next = max(next,find_best(aa, bb)); //更新next
    for(LL i = next;;i++){//枚举分母
        if((maxd+1-deep)*bb<=i*aa) break; //利用乐观估价函数来剪枝,从当前深度的接下来(maxd+1-deep)项分式,如果(1/i)*(maxd+1-deep)还凑不够aa/bb,则需要剪枝
        now[deep] = i;//更新当前深度的分母
        LL b2 = bb*i;//计算(aa/bb) - (1/i),通分后的分母是bb*i,分子是aa*i-bb
        LL a2 = aa*i-bb;
        LL g = gcd(a2, b2);//计算最大公约数,用于约分
        if(dfs(deep+1, i+1, a2/g, b2/g)) ok = true;
    }
    return ok;
}
void to_do(LL a,LL b){
    int check = 0;
    for(maxd = 1;;maxd++){
        memset(ans, -1, sizeof(ans));
        if(dfs(0,find_best(a,b),a,b)){
            check = 1;break;
        }
    }
    if(check){
        for(int i = 0;i<maxd;i++) printf("1/%lld+",ans[i]);
        printf("1/%lld\n",ans[maxd]);
    }else{
        printf("NO ANSWER!");
    }
}
int main(){
    LL a,b;
    while(scanf("%lld%lld",&a,&b)!=EOF) to_do(a,b);
    return 0;
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值