3.4 埃及分数
【问题描述】在古埃及,人们使用单位分数的和(形如 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 都大。
给出 a,b(0<a<b<1000),编程计算最好的表达方式(输入 a,b,按从小到大顺序输出所有分母)。
【分析】
1. 迭代加深搜索:本题由于搜索层数不明,用深搜极易陷入死胡同,用广搜空间又吃不消,这时迭代加深搜
索就成了考虑的对象。
2. 有序化:枚举的对象为分母,
,输出又要求有序,所以,不妨设
3. 定界法:设限定的搜索层数为 depth ,当前搜到第 k 层,当前正要枚举分母 ak ,还需枚举总和为 x/y
的分数。answer[d]表示当前最优解中的第 d 个分母,如果还没有得到解则表示正无穷。则必然有:
max{y/x,ak-1}+1≤ak≤min{(D-C+1)*y/x,Maxlongint①/x,answer[depth]-1}
枚举的初值容易得出,但终值的确定则要用到我们一开始对分母有序性的假设了。值得注意的是,直接限
界避免了搜索过程中屡次使用可行性剪枝,在一定程度上可以提高了程序的运行速度。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXDEPTH=10, maxlongint=2147483647;
int depth;
bool found;
int answer[MAXDEPTH], d[MAXDEPTH];
int gcd(int a, int b)
{
int t=a%b;
while(t)
{
a=b;
b=t;
t=a%b;
}
return b;
}
void search(int a, int b, int k)
{
int i,m,s,t;
if (k==depth+1)
return;
else if (b%a==0 && b/a>d[k-1])
{
d[k]=b/a;
if (!found || d[k]<answer[k])
memcpy(answer,d,sizeof(d)); // 更新最优解
found = true;
return;
}
// 确定上下界
s = max(b/a, d[k-1]) + 1;
t=(depth-k+1)*b/a;
t=min(t, maxlongint/b);
if (found) t=min(t,answer[depth]-1);
for (i=s; i<=t; i++)
{
// 从a/b中减去那个埃及分数
d[k]=i;
m=gcd(i*a-b, b*i);
search((i*a-b)/m, b*i/m, k+1);
}
}
int main()
{
int a,b;
int i;
found = false;
d[0] = 1;
cin>>a>>b;
for (depth=1; depth<=MAXDEPTH; depth++)
{
search(a,b,1);
if (found)
{
for(i=1;i<=depth;i++)
cout<<answer[i]<<" ";
cout<<endl;
break;
}
}
return 0;
}