算法竞赛入门经典:第七章 暴力求解法 7.16埃及分数

来源:互联网 发布:淘宝图片如何ps 编辑:程序博客网 时间:2024/05/19 03:42
/*埃及分数:使用单位分数的和(如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(0<a<b<1000),试编程计算最佳表达式。思路:若用宽度优先遍历,宽度没有上界,加数选择无界限。解决方案:采用迭代加深搜索:从小到大枚举深度上线d,每次只考虑深度不超过d的节点。只要解的深度有限,就能枚举到。深度上限作用:用来剪枝,分母递增顺序扩展,扩展到第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次迭代是根本不会考虑这颗子树的。扩展到第m层,前m-1个分数为e1,e2,..,em-1,那么em满足:a/b - e1 - e2 - ... - em-1 >= 1/em,确定em的下界若设定深度上限为d,当前为第m层,还剩d-m层,第m层之后的分母应该大于ema/b-e1-e2-...-em-1-em<(1/em)*(d-m)确定em的上界解答树模型:第0层:根  1  :1/2,1/3,1/4,...  2  :1/2的孩子是1/2+1/3,1/2+1/4,...      1/3的孩子是1/3+1/4,1/3+1/5,...  3  :1/2+1/3的孩子是1/2+1/3+1/4,1/2+1/3+1/5,...输入:19 45输出:19/45 = 1/5 + 1/6 + 1/18未解决:代码是别人的*//*关键:1 采用迭代加深搜索:从小到大枚举深度上线d,每次只考虑深度不超过d的节点。只要解的深度有限,就能枚举到。*/#include<stdio.h>  #include<string.h>  #include<stack>  using namespace std;      stack<int> s;  stack<int> sbest; //存放最优解    int flag;   //标志当前是否得到可行解    int gcd(int m,int n)    //求最大公约数  {      if(!n) return m;      else return gcd(n,m%n);  }  void minus(int &a,int &b,int c,int d)   //分数相减,结果为a/b  {      int m,n,k;      m = a*d - b*c;      n = b*d;      k = gcd(m,n);//这个起到的作用是化简a/b,消去最大公约数      a = m/k;       b = n/k;  }    int eq(int a,int b,int c,int d)     //判断两个分数是否相等  {      int m = a*d - b*c;      if(m > 0)          return 1;      else if( m == 0)          return 0;      else return -1;  }    void stackCopy(stack<int>& s1,stack<int> s2)    //将s2拷贝到s1中  {      stack<int> buf;       //中转站buf      while(!s1.empty())  //将s1清空          s1.pop();      while(!s2.empty())        {          buf.push(s2.top());          s2.pop();      }      while(!buf.empty())      {          s1.push(buf.top());          buf.pop();      }  }    void  dfs(int a,int b,int start ,int depth)   {      int n;      int c,d; int iEqual2,iEqual3;    if(depth == 0)          return ;      else      {          for(n = start;;n++)          {  int iEqual = eq(a,b,1,n);            if(iEqual == 0)              {                  s.push(n);                  if(!flag ||sbest.top() > s.top())    //如果当前没有解或者有更好的解                      stackCopy(sbest,s);                  flag = 1;                  s.pop();                  return ;              }              else if(iEqual > 0)              {                  if((iEqual3 = eq(a,b,depth,n)) >= 0)     //  a/b > 1/n * depth  ?确保能够更新depth层数                    return ;                  s.push(n);//?  说明走到这里的分支是1/n< a/b < 1/n*depth ,这里可以加n放入进去,找到了一个范围                c=a;d=b;                  minus(c,d,1,n);//a/b-1/n  消去前面已经加上的                dfs(c,d,n+1,depth-1);//这里由于做了减法,使得a/b减小了,在第一层继续寻找答案                 s.pop(); //为什么要弹出             }              else                  continue;          }      }  }    int main()  {      int a,b;      stack<int> buf;        scanf("%d%d",&a,&b);      for(int depth = 1;;depth++)      {          flag = 0;          while(!sbest.empty())              sbest.pop();          dfs(a,b,2,depth);          if(flag)              break;      }      while(!sbest.empty())      {          buf.push(sbest.top());          sbest.pop();      }      while(!buf.empty())      {          printf("%d ",buf.top());          buf.pop();      }      scanf("%d",&a);      return 0;  }  

0 0