埃及分数问题
- 题意
- 分析
- 思路
- 参考
- 代码
题意
在古埃及,人们使用单位分数的和(即1/a,a是自然数)表示一切有理
数。 例如,2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为在加数中不允许有相同的。
对于一个分数a/b,表示方法有很多种,其中加数少的比加数多的好,如果加数个数相
同,则最小的分数越大越好。 例如,19/45=1/5+1/6+1/18是最优方案。
输入整数a,b(0
分析
本题可以用dfs回溯来求解。但是由于本题没有指明等式数目即深度,如果dfs搜索的话是没有上限的,换句话说,如果用宽度优先遍历,连一层都扩展不完(因为每一层都是无限大的)。所以需要枚举深度,直到找到跳出即可,即迭代深度搜索。
深度上限maxd还可以用来“剪枝”。 按照分母递增的顺序来进行扩展,如果扩展到i层时,前i个分数之和为c/d,而第i个分数为1/e,则接下来至少还需要(a/b-c/d)/(1/e)个分数,总和才能达到a/b。 例如,当前搜索到19/45=1/5+1/100+…,则后面的分数每个最大为1/101,至少需要(19/45-1/5) / (1/101) =23项总和才能达到19/45,因此前22次迭代是根本不会考虑这棵子树的。 这里的关键在于:可以估计至少还要多少步才能出解。
注意,这里的估计都是乐观的,因为用了“至少”这个词。 说得学术一点,设深度上限为maxd,当前结点n的深度为g(n),乐观估价函数为h(n),则当g(n)+h(n)>maxd时应该剪枝。 这样的算法就是IDA*。 当然,在实战中不需要严格地在代码里写出g(n)和h(n),只需要像刚才
那样设计出乐观估价函数,想清楚在什么情况下不可能在当前的深度限制下出解即可。
思路
(1)枚举深度maxd
(2)dfs搜索:从满足1/c<=a/b的最小c即b/a+1开始枚举分母,深度d为表示第几个分数;每次计算的是a/b - 1/i = a2/b2 然后将a2,b2再作为a,b进行递归。
(3)剪枝:if(bb * (maxd+1-d) <= i*aa) break;//剪枝:如果剩下的maxd+1-d个分数全部都是1/i,加起来仍然不超过aa/bb,则无解! (maxd+1-d)/i <= aa/bb
参考
入门经典-埃及分数-P
#include<iostream>
#include<algorithm>
#include<memory.h>
using namespace std;
typedef long long ll;
const int maxn=100000;
ll ans[maxn];//保存最终结果
ll vis[maxn];//收索结果
ll resi[7];
ll a,b,k;
ll maxd; //搜索的层数
ll gcd(ll aa,ll bb) //最大公约数,通分使用
{
if(aa%bb==0) return bb;
return gcd(bb,aa%bb);
}
ll getmin(ll aa,ll bb)
{
ll x=1;
while(bb>aa*x)
x++;
return x;
}
bool ban(ll x)
{
if(k==0) return false;
for(int i=1;i<=k;i++)
if(x==resi[i]) return true;
return false;
}
bool better(ll d)
{
for(int i=d;i>=0;i--)
if(vis[i]!=ans[i]) return ans[i]==-1||vis[i]<ans[i]; // 更新
return false;
}
bool iddfs(ll d,ll from,ll aa,ll bb)
{
if(d==maxd)
{
if(bb%aa) return false;
vis[d]=bb/aa;
if(ban(bb/aa)) return false;
if(better(d)) memcpy(ans,vis,sizeof(ll)*(d+1));
return true;
}
bool ok=false;
from=max(from,getmin(aa,bb));
for(int i=from;;++i)
{
if(ban(i)) continue;
if(bb*(maxd+1-d)<=i*aa) break; //剪枝
vis[d]=i;
ll b2=bb*i;
ll a2=aa*i-bb;
ll g=gcd(a2,b2);
if(iddfs(d+1,i+1,a2/g,b2/g))ok=true;
}
return ok;
}
int t;
int main()
{
ios::sync_with_stdio(false);
cin>>t;
int cas=1;
while(t--)
{
cin>>a>>b>>k;
for(int i=1;i<=k;i++)
cin>>resi[i];
memset(vis,-1,sizeof vis);
cout<<"Case "<<cas++<<": "<<a<<"/"<<b<<'=';
for(maxd=1;;maxd++)
{
memset(ans,-1,sizeof ans);
if(iddfs(0,getmin(a,b),a,b)) break;
}
for(int i=0;i<=maxd;i++)
{
if(i!=0)
cout<<'+';
cout<<1<<"/"<<ans[i];
}
cout<<endl;
}
return 0;
}
更加详细的解答