#埃及分数

埃及分数

题目描述

在古埃及,人们使用单位分数的和(形如 1 a \dfrac{1}{a} a1 的, a a a 是自然数)表示一切有理数。如: 2 3 = 1 2 + 1 6 \dfrac{2}{3} = \dfrac{1}{2} + \dfrac{1}{6} 32=21+61,但不允许 2 3 = 1 3 + 1 3 \dfrac{2}{3} = \dfrac{1}{3} + \dfrac{1}{3} 32=31+31,因为加数中有相同的。对于一个分数 a b \dfrac{a}{b} ba,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。如:
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 \begin{aligned} \frac{19}{45} &= \frac{1}{3} + \frac{1}{12} + \frac{1}{180}\\ \frac{19}{45} &= \frac{1}{3} + \frac{1}{15} + \frac{1}{45}\\ \frac{19}{45} &= \frac{1}{3} + \frac{1}{18} + \frac{1}{30}\\ \frac{19}{45} &= \frac{1}{4} + \frac{1}{6} + \frac{1}{180}\\ \frac{19}{45} &= \frac{1}{5} + \frac{1}{6} + \frac{1}{18}\\ \end{aligned} 45194519451945194519=31+121+1801=31+151+451=31+181+301=41+61+1801=51+61+181
最好的是最后一种,因为 1 18 \dfrac{1}{18} 181 1 180 , 1 45 , 1 30 \dfrac{1}{180}, \dfrac{1}{45}, \dfrac{1}{30} 1801,451,301 都大。
注意,可能有多个最优解。如:
59 211 = 1 4 + 1 36 + 1 633 + 1 3798 59 211 = 1 6 + 1 9 + 1 633 + 1 3798 \begin{aligned} \frac{59}{211} &= \frac{1}{4} + \frac{1}{36} + \frac{1}{633} + \frac{1}{3798}\\ \frac{59}{211} &= \frac{1}{6} + \frac{1}{9} + \frac{1}{633} + \frac{1}{3798}\\ \end{aligned} 2115921159=41+361+6331+37981=61+91+6331+37981
由于方法一与方法二中,最小的分数相同,因此二者均是最优解。

给出 a , b a,b a,b,编程计算最好的表达方式。保证最优解满足:最小的分数 ≥ 1 1 0 7 \ge \cfrac{1}{10^7} 1071

输入格式

一行两个整数,分别为 a a a b b b 的值。

输出格式

输出若干个数,自小到大排列,依次是单位分数的分母。

样例 #1

样例输入 #1

19 45

样例输出 #1

5 6 18

提示

1 < a < b < 1000 1 \lt a \lt b \lt 1000 1<a<b<1000

读题

明显的IDA*,考虑表示分数,用两个long long表示分数即可,此题便简单了

代码

#include <bits/stdc++.h>
using namespace std;
int a, b, maxd;
long long gcd(long long a, long long b) { return b == 0 ? a : gcd(b, a % b); }
inline int get_first(long long a, long long b) { return b / a + 1; }
const int maxn = 105;
long long v[maxn], ans[maxn];
bool better(int d)
{
    for (int i = d; i >= 1; i--)
        if (v[i] != ans[i])
            return  ans[i]==-1 or v[i] < ans[i];
    return false;
}
bool dfs(int d, int from, long long aa, long long bb)
{
    if (d == maxd)
    {
        if (bb % aa)
            return false; 
        v[d] = bb;
        if (better(d)) 
            memcpy(ans, v, sizeof(long long) * (d + 1));
        return true;
    }
    bool ok = false;
    from = max(from, get_first(aa, bb)); 
    for (int i = from; not (bb * (maxd + 1 - d) <= i * aa ); i++)
    {
        v[d] = i;
        long long b2 = bb * i;
        long long a2 = aa * i - bb;
        long long g = gcd(a2, b2); 
        if (dfs(d + 1, i + 1, a2 / g, b2 / g))
            ok = true;
    }
    return ok;
}
int main()
{
    cin >> a >> b;
    for (maxd = 1;; maxd++)
    { 
        memset(ans, -1, sizeof(ans));
        if (dfs(1, get_first(a, b), a, b)) break;
    }
    for (int i=1;i<=maxd;i++)
        cout << ans[i] << " ";
    return 0;
}

分析

get_first()函数

1.对于分数 a b \dfrac{a}{b} ba,我们选出一个最大的埃及分数 1 c \dfrac{1}{c} c1,来做为答案中的一个数则
1 c ≤ a b \frac{1}{c} \le \frac{a}{b} c1ba
这就是get_first的意义,很容易推出:
b a ≤ c \frac{b}{a} \le c abc
但算竞不是数竞,由于long long不存小数点后的,故改成b / a + 1

对于选择分数的限制

1限制

对于分数 a b \dfrac{a}{b} ba,我们选出一个最大的埃及分数 1 c \dfrac{1}{c} c1,使用get_first(a,b)解决此限制,并求出枚举的下界

2限制

我们要保证输出的分母从小到大,所以要保存上一个数

数组说明

1.v数组保存临时答案,但要求最终答案的最小的分母最大,需要与保存的ans数组比较谁最优,使用better函数
2.

bool better(int d)
{
    for (int i = d; i >= 1; i--)
        if (v[i] != ans[i])
            return  ans[i]==-1 or v[i] < ans[i];
    return false;
}
d表示数列的长度

由于分数从大到小选,输出的分母序列从小到大,使用for,从d倒着比较,如果临时数组中的数小,则更新答案。为什么ans[i]==-1也要返回true呢,

memset(ans, -1, sizeof(ans));

我们初始化所有的ans为-1,若为-1,则说明没填数,直接覆盖。

dfs()函数

bool dfs(int d, int from, long long aa, long long bb)
{
    if (d == maxd)
    {
        if (bb % aa)
            return false; 
        v[d] = bb;
        if (better(d)) 
            memcpy(ans, v, sizeof(long long) * (d + 1));
        return true;
    }
    bool ok = false;
    from = max(from, get_first(aa, bb)); 
    for (int i = from; not (bb * (maxd + 1 - d) <= i * aa ); i++)
    {
        v[d] = i;
        long long b2 = bb * i;
        long long a2 = aa * i - bb;
        long long g = gcd(a2, b2); 
        if (dfs(d + 1, i + 1, a2 / g, b2 / g))
            ok = true;
    }
    return ok;
}

d表示选的第几个数,from保存上一个分母,aa/bb,是还要凑的分数
如果都选 1 i \dfrac{1}{i} i1,能选 m a x d − d + 1 maxd-d+1 maxdd+1个,但仍然小于 a a b b \dfrac{aa}{bb} bbaa,即

a a b b ≥ 1 i ∗ ( m a x d − d + 1 ) \frac{aa}{bb} \geq \frac{1}{i}*(maxd-d+1) bbaai1(maxdd+1)
则不可能选择i
便是

bb * (maxd + 1 - d) <= i * aa

如何运算分数呢?
a a b b − 1 i = i ∗ a a − b b i ∗ b b \frac{aa}{bb}-\frac{1}{i}=\frac{i*aa-bb}{i*bb} bbaai1=ibbiaabb
便是:

 		long long b2 = bb * i;
        long long a2 = aa * i - bb;
        long long g = gcd(a2, b2); 

记得约分:dfs(d + 1, i + 1, a2 / g, b2 / g)

from = max(from, get_first(aa, bb)); 

由于对于选择分数的限制都要满足,便取最大数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值