CodeVS1288埃及分数(IDA*)
在古埃及,人们使用单位分数的和(形如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[200],zui=1e9,ans[200]; bool dfs(int dep,ll x,ll y,ll xia){ if(dep==deep){ if(x==1&&y>=xia){ num[deep]=y; if(num[deep]<zui){ for(int i=1;i<=deep;++i)ans[i]=num[i]; zui=ans[deep]; } return 1; } return 0; } if(!x)return 0; ll shang=ceil(y*(deep-dep+1)/x); bool tag=0; for(int i=xia;i<=shang;++i){ ll aa=x*i-y,bb=y*i;if(aa<0)continue; ll gg=gcd(aa,bb); aa/=gg;bb/=gg; num[dep]=i; if(dfs(dep+1,aa,bb,i+1))tag=1; } return tag; } int main(){ scanf("%lld%lld",&a,&b); if(a==1){ printf("%lld %lld",a,b); return 0; } for(deep=2;;++deep)if(dfs(1,a,b,1))break; for(int i=1;i<=deep;++i)printf("%lld ",ans[i]); return 0; }