埃及分数问题 【IDA*】

埃及分数问题

  • 题意
  • 分析
  • 思路
  • 参考
  • 代码

题意

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

分析

本题可以用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,则无解! (maxd+1-d)/i <= aa/bb

参考

入门经典-埃及分数-P 

代码

#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int maxn = 1000;
LL ans[maxn],v[maxn];
int maxd,kcase;
int gcd(LL a,LL b){//最大公约数,用于约分
    LL temp;
    while(b){temp = b;b = a % b;a = temp;}
    return a;
}
LL get_first(LL a, LL b){//满足1/c<=a/b的最小c
    return b/a + 1;
}
bool better(int d){//更新最优值:如果当前解v比目前最优解ans更优,更新ans
    for(int i=d;i>=0;i--)
        if(v[i] != ans[i])
            return ans[i] == -1 || v[i] < ans[i];
    return false;
}
//当前深度为d,分母不能小于from,分数之和恰好为aa/bb
bool dfs(int d,int from,LL aa,LL bb){
    if(d == maxd){//深度达到当前枚举个数
        if(bb % aa) return false;//aa/bb必须是埃及分数
        v[d] = bb/aa;//保存分母
        if(better(d)) memcpy(ans,v,sizeof(LL)*(d+1));//更新最优解
        return true;//返回成功
    }
    bool ok = false;//用于返回本次递归的结果
    from = max((LL)from,get_first(aa,bb));//枚举的起点
    for(int i=from; ;i++){
        if(bb * (maxd+1-d) <= i*aa) break;//剪枝:如果剩下的maxd+1-d个分数全部都是1/i,加起来仍然不超过aa/bb,则无解! (maxd+1-d)/i <= aa/bb
        v[d] = i;//保存分母
        //计算aa/bb - 1/i,设结果为a2/b2
        LL b2 = bb*i;
        LL a2 = aa*i - bb;
        LL g = gcd(a2,b2);//用于约分
        if(dfs(d+1,i+1,a2/g,b2/g)) ok = true;//找到解返回成功
    }
return ok;}

void solve(int a,int b){
    int ok = 0;
    for(maxd = 1; ;maxd++){//枚举深度,即等式相加的个数
        memset(ans,-1,sizeof(ans));
        if(dfs(0,get_first(a,b),a,b)){//递归找到时返回成功
            ok = 1;break;//标记并退出枚举
        }
    }
    printf("Case %d: %d/%d=",++kcase,a,b);
    if(ok) {for(int i=0;iprintf("1/%lld+",ans[i]);printf("1/%lld\n",ans[maxd]);}
    else printf("%d/%d\n",a,b);
}
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF)
        solve(a,b);
    return 0;
}

你可能感兴趣的:(------搜索-------,UVa,第7章,暴力求解法,IDA*)