您的位置:首页 > 其它

埃及分数问题 IDA*

2018-03-18 21:05 302 查看

  题意

在古埃及,人们使用单位分数的和(即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。

分析

本题可以用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,则无解#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<sstream>
#include<map>
#include<stack>
using namespace std;
typedef long long LL;
const LL BIG = 1000;
LL ans[BIG],now[BIG],maxd;
LL find_best(LL a,LL b){ //满足1/c<=a/b的最小c
return b/a+1;
}
LL gcd(LL a,LL b){
return b == 0 ? a : gcd(b, a%b); //求最大公约数
}
bool better(LL depth){ //更新最优值,如果当前解now比之前最优解ans更优,则更新ans
for(LL d = depth;d>=0;d--)
if(now[d]!=ans[d]) return ans[d]==-1||now[d]<ans[d];//(1)此时的深度尚未找到解(2)当前的分母小于之前解的分母,说明当前深度为d的分数比之前的分数大,则可以替换。否则不替换
return false;
}
//当前深度为deep,接下来的分母不能小于next,接下来的分式之和恰好为aa/bb
bool dfs(LL deep,LL next,LL aa,LL bb){
bool ok;
if(deep==maxd){//此时到达了最后一层
if(bb%aa) return false; //bb不能整除aa(aa!=1),构成不了埃及分式
now[deep] = bb/aa;//取分母
if(better(deep)) memcpy(ans, now, sizeof(long long)*(deep+1));//找到了更优的解,更新ans
return true;
}
ok = false;
next = max(next,find_best(aa, bb)); //更新next
for(LL i = next;;i++){//枚举分母
if((maxd+1-deep)*bb<=i*aa) break; //利用乐观估价函数来剪枝,从当前深度的接下来(maxd+1-deep)项分式,如果(1/i)*(maxd+1-deep)还凑不够aa/bb,则需要剪枝
now[deep] = i;//更新当前深度的分母
LL b2 = bb*i;//计算(aa/bb) - (1/i),通分后的分母是bb*i,分子是aa*i-bb
LL a2 = aa*i-bb;
LL g = gcd(a2, b2);//计算最大公约数,用于约分
if(dfs(deep+1, i+1, a2/g, b2/g)) ok = true;
}
return ok;
}
void to_do(LL a,LL b){
int check = 0;
for(maxd = 1;;maxd++){
memset(ans, -1, sizeof(ans));
if(dfs(0,find_best(a,b),a,b)){
check = 1;break;
}
}
if(check){
for(int i = 0;i<maxd;i++) printf("1/%lld+",ans[i]);
printf("1/%lld\n",ans[maxd]);
}else{
printf("NO ANSWER!");
}
}
int main(){
LL a,b;
while(scanf("%lld%lld",&a,&b)!=EOF) to_do(a,b);
return 0;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: