习题7-7:埃及分数(迭代加深)

习题7-7:埃及分数
题意:
对一个分数(n/m)将它分解成若干个不相等的单分子分数(即分子为1)。求最少能分解成哪几个分数相加。若有多解,输出最大的分母尽量的小的解。会有k个禁止使用的单分子分数。
思路:
这题搜索的特点就是深度和宽度都不确定,首先对于一个a/b,你不知道他的最优解有几个1/a类似形式的分数构成,这是宽度。对于每个1/i他的深度也是不确定的,唯一剪枝的条件就是确定深度后余下都选择1/i之和小于a/b。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<sstream>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
int dir[4][2] = {1,0,0,1,-1,0,0,-1};
int dir2[8][2] = {1,1,1,-1,-1,1,-1,-1,1,0,0,1,-1,0,0,-1};
const int inf = 1 << 30;
const int maxn = 1e5 + 10;
const double eps = 1e-8;
ll v[maxn], ans[maxn];
ll maxd;
set<ll>q;
ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b , a % b);
}
ll getfirst(ll a, ll b)
{
    for(ll i = 2; ;i++)
    {
        if(q.count(i))continue;///如果是有分母为禁止的分母,continue
        if(b < a * i)return i;
    }
}
bool better(ll d)
{
    for(ll 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(ll d, ll from, ll aa, ll bb)
{
    if(d == maxd)
    {
        if(aa != 1)return false;///必须是埃及分数
        if(q.count(bb))return false;///如果是有分母为禁止的分母,返回假
        v[d] = bb;
        if(better(d))memcpy(ans, v, sizeof(ll) * (d + 1));
        return true;
    }
    bool ok = false;
    from = max(from, getfirst(aa, bb));///枚举起点,去上一次加一的分母值和比a/b小的最大分数的分母中更大的。
    for(ll i = from; ; i++)
    {
        if(q.count(i))continue;///如果是有分母为禁止的分母,continue
        ///剪枝,如果c/d(前i个分数之和)+1/e*(maxd-i)<a/b,可以直接break;
        if(bb * (maxd + 1 - d) <= i * aa)break;
        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;
}
int main()
{
    ll a, b, c, x;
    int t, cases = 0;
    cin >> t;
    while(t--)
    {
        q.clear();
        cin >> a >> b >> c;
        while(c--)
        {
            cin >> x;
            q.insert(x);
        }
        for(maxd = 1;;maxd++)
        {
            memset(ans, -1, sizeof(ans));
            if(dfs(0, getfirst(a, b), a, b))break;
        }
        printf("Case %d: %lld/%lld=",++cases, a, b);
        for(ll i = 0; i <= maxd; i++)
        {
            if(i)cout<<"+";
            cout<<"1/"<<ans[i];
        }
        cout<<endl;
    }
    return 0;
}
///这个程序是反向搜索,
///59 211 答案有两个为
///4 36 633 3798 和 6 9 633 3798,反向搜索的话输出第一组数据,正向输出第二组
/*#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<sstream>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
int dir[4][2] = {1,0,0,1,-1,0,0,-1};
int dir2[8][2] = {1,1,1,-1,-1,1,-1,-1,1,0,0,1,-1,0,0,-1};
const int inf = 1 << 30;
const int maxn = 1e5 + 10;
const double eps = 1e-8;
ll v[maxn], ans[maxn];
ll maxd;
ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b , a % b);
}
///还有d层,分母不能小于from,分数之和为aa/bb
bool dfs(ll d, ll from, ll aa, ll bb)
{
    if(aa < 0)return false;
    if(d == 1)
    {
        if(aa != 1)return false;///必须是埃及分数
        if(bb < from)return false;
        if(bb < ans[1])
        {
            ans[1] = bb;
            for(int i = maxd; i >= 2; i--)ans[i] = v[i];
        }
        return true;
    }
    bool ok = false;
    for(ll i = from; ; i++)
    {
        ///剪枝,如果c/d(前i个分数之和)+1/e*(maxd-i)<a/b,可以直接break;
        if(bb * d < i * aa)break;
        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;
}
int main()
{
    ll a, b, aa, bb;
    cin >> a >> b;
    ll g = gcd(a, b);
    aa = a / g;
    bb = b / g;
    if(aa == 1)cout<<bb<<endl;
    else
    {
        for(maxd = 1;;maxd++)
        {
            ans[1] = 1e17;
            if(dfs(maxd, 1, aa, bb))break;
        }
        for(ll i = maxd; i; i--)
        {
            cout<<ans[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值