埃及分数拆分——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<iostream>
#include<algorithm>
#include<memory.h>
using namespace std;
typedef long long ll;
const int maxn=100000;
ll ans[maxn];//保存最终结果
ll vis[maxn];//收索结果
ll resi[7];
ll a,b,k;
ll maxd;  //搜索的层数
ll gcd(ll aa,ll bb) //最大公约数,通分使用
{
    if(aa%bb==0) return bb;
        
    return gcd(bb,aa%bb);
}
ll getmin(ll aa,ll bb)
{
    ll x=1;
    while(bb>aa*x)
        x++;
    return x;
}
bool ban(ll x)
{
    if(k==0) return false;
    for(int i=1;i<=k;i++)
        if(x==resi[i]) return true;
    return false;
}
bool better(ll d)
{
    for(int i=d;i>=0;i--)
        if(vis[i]!=ans[i]) return ans[i]==-1||vis[i]<ans[i];  // 更新
    return false;
}
bool iddfs(ll d,ll from,ll aa,ll bb)
{
    if(d==maxd)
    {
        if(bb%aa) return false;
        vis[d]=bb/aa;
        if(ban(bb/aa)) return false;
        if(better(d)) memcpy(ans,vis,sizeof(ll)*(d+1));
        return true;
    }
    bool ok=false;
    from=max(from,getmin(aa,bb));
    for(int i=from;;++i)
    {
        if(ban(i)) continue;
        if(bb*(maxd+1-d)<=i*aa)  break;  //剪枝

        vis[d]=i;

        ll b2=bb*i;
        ll a2=aa*i-bb;
        ll g=gcd(a2,b2);
        if(iddfs(d+1,i+1,a2/g,b2/g))ok=true;

    }
    return ok;
}
int t;
int main()
{
    ios::sync_with_stdio(false);
    cin>>t;
    int cas=1;
    while(t--)
    {
        cin>>a>>b>>k;
        for(int i=1;i<=k;i++)
            cin>>resi[i];

        memset(vis,-1,sizeof vis);
        cout<<"Case "<<cas++<<": "<<a<<"/"<<b<<'=';
        for(maxd=1;;maxd++)
        {
            memset(ans,-1,sizeof ans);
            if(iddfs(0,getmin(a,b),a,b)) break;
        }
        for(int i=0;i<=maxd;i++)
        {
            if(i!=0)
                cout<<'+';
            cout<<1<<"/"<<ans[i];
        }
        cout<<endl;
    }
    return 0;
}



更加详细的解答

猜你喜欢

转载自blog.csdn.net/codetypeman/article/details/80025330