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;
}

 

posted @ 2018-10-11 10:54  comld  阅读(297)  评论(0编辑  收藏  举报