在古埃及,人们使用单位分数的和(形如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),编程计算最好的表达方式。
Solution
一道非常好的迭代加深搜索例题。
首先我们枚举一个深度deep,这就是迭代加深的核心所在,如果我们不控制深度,他会产生质数级别的多余状态,但如果我们控制深度,虽然一个状态会被多次枚举到,但只是常数级别的,有非常好的优化的效果。
然后可以进行爆搜,A*在这里体现的就是剪枝。我们把分数从大到小枚举;
剪枝一:我们枚举要有上界,假设我们还有x个数,当前分数为a/b,那么我们枚举上界为y*num/x.
剪枝二:当x=1时搜索就可以结束了。
Code
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
ll deep,a,b,num[],zui=1e9,ans[];
bool dfs(int dep,ll x,ll y,ll xia){
if(dep==deep){
if(x==&&y>=xia){
num[deep]=y;
if(num[deep]<zui){
for(int i=;i<=deep;++i)ans[i]=num[i];
zui=ans[deep];
}
return ;
}
return ;
}
if(!x)return ;
ll shang=ceil(y*(deep-dep+)/x);
bool tag=;
for(int i=xia;i<=shang;++i){
ll aa=x*i-y,bb=y*i;if(aa<)continue;
ll gg=gcd(aa,bb);
aa/=gg;bb/=gg;
num[dep]=i;
if(dfs(dep+,aa,bb,i+))tag=;
}
return tag;
}
int main(){
scanf("%lld%lld",&a,&b);
if(a==){
printf("%lld %lld",a,b);
return ;
}
for(deep=;;++deep)if(dfs(,a,b,))break;
for(int i=;i<=deep;++i)printf("%lld ",ans[i]);
return ;
}