《算法竞赛入门经典》7.5 埃及分数

在古埃及,人们使用单位分数的和(形如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),编程计算最好的表达方式。


方法是采用迭代加深搜索(iterative deeping),从小到大枚举深度上限d,每次只考虑深度不超过d的结点,按照分母递增的顺序来进行扩展。

但是这样还不够,我们还开始加一些限制条件进行剪枝:

比如扩展到第m层,前m-1个数分别为E1,E2......Em-1,那么Em应该满足 a/b - 1/E1 - 1/E2 -  ...... - 1/Em-1 > 1/Em,这样就确定了Em的下界

如果设定的深度上限为d,当前为第m层,那么还剩 d - m 层,第m层之后的分母应该大于Em,

所以还应该满足  a/b - 1/E1 - 1/E2 -  ...... - 1/Em-1 < (1/Em) * (d -m+1),这样就确定了Em的上界



示例代码如下(分母使用栈存放的,可以改为数组):

#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 = 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;
	if(depth == 0)
		return ;
	else
	{
		for(n = start;;n++)
		{
			if(eq(a,b,1,n) == 0)
			{
				s.push(n);
				if(!flag ||sbest.top() > s.top())	//如果当前没有解或者有更好的解
					stackCopy(sbest,s);
				flag = 1;
				s.pop();
				return ;
			}
			else if(eq(a,b,1,n) > 0)
			{
				if(eq(a,b,depth,n) >= 0)		//  a/b > 1/n * depth
					return ;
				s.push(n);
				c=a;d=b;
				minus(c,d,1,n);
				dfs(c,d,n+1,depth-1);
				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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值