在古埃及,人们使用单位分数的和(形如1/a的,a是自然数)表示一切有理数。例如2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为家属中间有相同的。对于一个分数a/b,表示方法有很多种,哪一种最好呢?首先加数少的比加数多的好,其次,加数个数相同的,最小分数越大越好。
例如:
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;
最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180大。
输入 19 45
输出 5 6 18
#include <iostream>
#include<algorithm>
using namespace std;
#define ll long long
ll a, b;
ll pre[100];//储存当前数组
ll ans[100];//储存目标数组
//设置的最大层数,超过这个层数减枝
int maxl;
//辗转相除法求取最大公因数
ll gcd(ll m, ll n) {
if(n == 0){
return m;
}
return gcd(n, m % n);
}
//估值函数
//m表示现在第几层
bool value(int m) {
for (int i = m; i >= 1; i++) {
if (pre[i] != ans[i]) {
return ans[i] == -1 || ans[i] >pre[i];
}
}
return false;
}
//将当前数组的值覆盖目标数组
void copy(ll to[], ll from[], int l) {
for (int i = 1; i <= l; i++) {
to[i] = from[i];
}
}
ll fs(ll fzz, ll fmm) {
return fmm / fzz + 1;
}
//递归调用
bool dfs(int l, ll bef, ll fz, ll fm) {
if (l == maxl) {
if (fz != 1) {
return false;
}
pre[l] = fm;
if (value(l)) {
copy(ans, pre, l);
}
return true;
}
bool f = false;
bef = max(bef, fs(fz, fm));
for (ll i = bef; (maxl - l + 1) * fm > fz * i; i++) {
pre[l] = i;
ll fzz = fz * i - fm;
ll fmm = fm * i;
ll maxd = gcd(fzz, fmm);//约分
if (dfs(l + 1, bef + 1, fzz / maxd, fmm / maxd)) {
f = true;
}
}
return f;
}
int main()
{
cin >> a >> b;
maxl = 1;
while (maxl++) {//从1开始枚举数列长度
memset(ans, -1, sizeof(ans));//先都打上标记(-1指未被赋值)
if (dfs(1, fs(a, b), a, b)) {
break;
}
}
for (int i = 1; i <= maxl; i++) {//输出答案数组
cout << ans[i] << " ";
}
return 0;
}
for循环判断条件fm*(maxl-l+1)>i*fz,假设后面的分数都是1/i,如果在指定的层数之前,1/i×剩余的层数都无法大于指定的分数,则减枝,以为后边的分数都要比此时1/i要小。
value中函数为什么要倒搜,因为个数相同的分数,分母最小的为最佳。