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

/*
埃及分数:
使用单位分数的和(如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层之后的分母应该大于em
a/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;  
}  

你可能感兴趣的:(算法竞赛)