埃及分数
题目描述
在古埃及,人们使用单位分数的和(形如
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}
c1≤ba
这就是get_first
的意义,很容易推出:
b
a
≤
c
\frac{b}{a} \le c
ab≤c
但算竞不是数竞,由于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
maxd−d+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)
bbaa≥i1∗(maxd−d+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}
bbaa−i1=i∗bbi∗aa−bb
便是:
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));
由于对于选择分数的限制都要满足,便取最大数。