限界深搜——埃及分数 两种剪枝

1 篇文章 0 订阅
1 篇文章 0 订阅
Description
问题描述:在古埃及,人们使用单位分数的和(形如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, 编程计算最好的表达方式。
Input
输入:a b
Output
输出:若干个数,自小到大排列,依次是单位分数的分母。
Sample Input
19 45
Sample Output
5 6 18
Hint
【数据规模】

0<a<b<1000


题目本身是一个简单的限界深搜,每次枚举分母,每次x/y-1/i,直到当前深度=maxdepth的时候,判断最后剩下的分数,也即最后一个答案是否符合埃及分数的约定。

有两个至关重要的剪枝:

1.保证枚举的单调性——每次从小于x/y的最大分数从大到小开始枚举,以节省枚举时空。

2.基于剪枝1,如果“即使以后的每次都加上当前枚举分数(保证了最大)都<=待减分数”,则这个枚举是无效的,可以忽略。

//涉及gcd,用了long long 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<cmath>
using namespace std;
typedef long long ll;
ll A,B,mn,maxdepth,temp[100005]={0},ans[100005]={0};
ll GCD(ll a,ll b)
{
	if(!b)return a;
	return GCD(b,a%b);
}
ll getst(ll x,ll y)//找到比 x/y 小的 最大的 1/i 
{
	for(ll i=2;;i++)if(x*i>y)return i;
}
bool judge(ll lim)
{
	if(ans[0]==0)return 1;//答案数组为空 
        return(temp[lim]<ans[lim]);//因为保证了递减枚举,则temp[lim]肯定为最小的一个,现在要求最小分数最大,即分母最小。 
	return 0;
}
bool DFS(ll depth,ll minn,ll x,ll y)
{
	bool flag=0;
	if(depth==maxdepth)
	{
		if(y%x)return 0;//不符合埃及分数的定义,如2/3,4/5;而1/2,2/4,4/8等都是符合要求的。 
		temp[depth]=y/x;//注意约分 
		if(judge(depth))
		memcpy(ans,temp,sizeof(temp));//存在更优解,更新答案 
		return 1;
	}
	minn=max(minn,getst(x,y));//取得新下界,注意这里取的是max(分母),以防止漏除枚举。 
	for(ll i=minn;;i++)
	{
		if((maxdepth-depth+1)*y<=x*i)break;//剪枝2,这里把(maxdepth-depth+1)*(1/i)<=x/y 做了变形。 
		ll newy=y*i;
		ll newx=x*i-y;//做差 
		temp[depth]=i;//枚举解放入temp数组 
		ll gcd=GCD(newx,newy);//取得新分数分子分母最小公因数,以便约分。 
		if(DFS(depth+1,minn+1,newx/gcd,newy/gcd))flag=1;//minn一定要+1,不然会出现重复枚举
		//如果下一层搜索结果可行,则说明当前枚举可行 
	}
	return flag;
}
void work(void)
{
	scanf("%lld%lld",&A,&B);
	for(maxdepth=1;;maxdepth++)
	{
		mn=getst(A,B);
		memset(temp,0,sizeof(temp));
		memset(ans,0,sizeof(ans));//不要忘了清空ans数组,每一个层次都可能存在最优解 
		if(DFS(0,mn,A,B))break;//要求加数最少,找到一个可行解就退出 
	}
	for(ll i=0;i<=maxdepth;i++)printf("%lld ",ans[i]);//不要忘了从0开始输出。 
}
int main(){
    work();
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值