埃及分数问题(迭代加深搜索)

题目:http://acm.nefu.edu.cn/JudgeOnline/problemshow.php?problem_id=358


题意:给你个真分数,你需要将其化简为最少的若干特殊真分数之和,你要输出这个序列(序列按递增序)。如果有不同的方案,则分数个数相同的情况下使最大的分母最小。若还相同,则使次大的分母最大……以此类推。如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。对于一个分数a/b,表示方法有很多种,但是哪种最好呢? 首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。


分析:此题目解答树宽度和深度都没有上限,不能直接用bfs或者dfs。

解决方案是采用迭代加深搜索:从小到大枚举深度上限。

迭代加深搜索,实质上是限定下界的深度优先搜索。即首先允许深度优先搜索K层,若没有发现可行解,再将K+1后

重复以上步骤搜索,直到搜索到可行解。

在迭代加深搜索的算法中,连续的深度优先搜索被引入,每一个深度约束逐次加1,直到搜索到目标为止。这样可以

看出重复搜索了好多。但是它的好处在于:

1.空间开销小:每个深度下实际上是一个深度优先搜索,不过深度有限制,而DFS的空间消耗小是众所周知的。

2.利于深度剪枝。

3.时间效率不低虽然重复搜索,但是大家不难理解,前一次搜索跟后一次相不是微不足到的。

此题中,深度上限还可以用来“剪枝”。按照分母递增的顺序来进行扩展,如果扩展到第i层时,前i个分数之和为c/d,而第i个分数为1/e,则接下来至少还需要(a/b-c/d)(1/e)个分数,综合才能达到(a/b)。

<pre name="code" class="cpp">//迭代加深搜索
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cassert>
using namespace std;
int a,b,maxd;
//约分
long long yf(long long a,long long b){
  return b==0?a:yf(b,a%b);
}
// 返回满足1/c <= a/b的最小c
int find_first(long long a,long long b) {
  return b/a+1;
}
const int maxn=100+5;
long long v[maxn],ans[maxn];
// 如果当前解v比目前最优解ans更优,更新ans
bool better(int d){
  for(int i=d;i>=0;i--){
    if(v[i]!=ans[i]) {
        return ans[i]==-1||v[i]<ans[i];
    }
  }
  return false;
}
// 当前深度为d,分母不能小于from,分数之和恰好为aa/bb
bool dfs(int d,int from,long long aa,long long bb){
  if(d==maxd) {//判断是否有解
    if(bb%aa) return false; // aa/bb必须是埃及分数
    v[d]=bb/aa;
    if(better(d)){
        memcpy(ans,v,sizeof(long long)*(d+1));
    }
    return true;
  }
  bool ok=false;
  from=max(from,find_first(aa,bb)); // 枚举的起点
  for(int i=from;;i++) {
    // 剪枝:如果剩下的maxd+1-d个分数全部都是1/i,加起来仍然不超过aa/bb,则无解
    if(bb*(maxd+1-d)<=i*aa){
        break;
    }
    v[d]=i;
    // 计算aa/bb - 1/i,设结果为a2/b2
    long long b2=bb*i;
    long long a2=aa*i-bb;
    long long g=yf(a2,b2); // 以便约分
    if(dfs(d+1,i+1,a2/g,b2/g)){
        ok=true;//如果有解就将ok的值改为true,从而判断是否有解
    }
  }
  return ok;
}
int main()
{
  int kase=0;//统计次数
  while(cin>>a>>b){
    int ok=0;
    for(maxd=1;maxd<=100;maxd++){
      memset(ans,-1,sizeof(ans));//将答案数组清空
      if(dfs(0,find_first(a,b),a,b)){//通过迭代加深搜索来寻求答案
            ok = 1;
            break;
      }
    }
    cout<<"Case "<<++kase<<": ";
    if(ok){
      cout<<a<<"/"<<b<<"=";
      for(int i=0;i<maxd;i++){
        cout<<"1/"<<ans[i]<<"+";
      }
      cout<<"1/"<<ans[maxd]<<"\n";
    }
    else{
        cout<<"No solution.\n";
    }
  }
  return 0;
}



                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值