埃及分数 题解

题目传送门

题目大意: 将一个分数 a b \dfrac a b ba 分解成若干个单位分数 1 x \dfrac 1 x x1 的和,要求找到加数个数最少且最小的单位分数最大的方案。

题解

显然,我们不知道搜索的深度是多少,所以我们需要使用迭代加深搜索。

但是这样还不够,难道我们每次枚举分母的时候真的要枚举到 1 0 7 10^7 107 吗?(那怕不是傻吧。。)于是,我们还需要几个优化。

首先优化分母枚举的下限

优化1

我们设当前减去若干个单位分数后得到的分数是 x y \frac x y yx,当前枚举的分母为 i i i

显然,一个优化就是每次的分母不可能比上一次的分母要小,于是可以从上一次的分母 + 1 +1 +1 开始枚举。

优化2

但这还不够,还有一个优化,就是每一次选取的分数不能大于 x y \frac x y yx,也就是要满足 1 i < x y \frac 1 i < \frac x y i1<yx。为了把代码写的简洁一点并且避免精度误差,我们把柿子变换一下:
1 i < x y y < x i \frac 1 i < \frac x y\\ yi1<yxy<xi
于是最后只需要从这两个优化中取个最大值,就知道下限了。


接下来是优化上限

优化3

假设我们现在还需要选 k k k 个数。

因为后面的分母会越来越大,分数会越来越小,而我们要保证剩下的这 k k k 个分数加起来是可以等于 x y \frac x y yx 的,所以,对于当前选取的分母 i i i,我们对它有这样一个要求: k i > x y \frac k i> \frac x y ik>yx,也就是说,假设剩下的 k k k 个分数全部都是 1 i \frac 1 i i1,那么这些分数加起来肯定大于 x y \frac x y yx

如果剩下的 k k k 个数都是 1 i \frac 1 i i1 而他们加起来小于等于 x y \frac x y yx 的话,那么因为实际上这 k k k 个数一个比一个小,他们加起来一定小于 1 i \frac 1 i i1,所以它们加起来也肯定小于 x y \frac x y yx。那这样的话往下肯定搜不到答案,所以我们在这个时候要停止。

再变换一下柿子:
k i > x y k y > x i \frac k i> \frac x y\\ ky>xi ik>yxky>xi
有了这两个限制条件,我们就可以快乐地搜索了~

代码如下:

#include 
#include 
#include 
#define int long long//懒得一个个改于是就只好暴力一点了……

int a,b;
int ans[10010],anss=0;
int s[10010],t=0;
int least(int x,int y)//求出第一个小于x/y的单位分数的分母(优化2)
{
	for(int i=2;;i++)
	if(x*i>y)return i;
}
int gcd(int x,int y){return y==0?x:gcd(y,x%y);}
int maxx(int x,int y){return x>y?x:y;}
bool dfs(int re,int x,int y)//re表示还要选re个数(也就是上面的k)
{
	if(re==1)//当只剩最后一个数时,显然最后这个数我们只能选x/y
	{
		//如果x/y是个单位分数并且它比之前的解要更加优秀
		if(x==1&&y>s[t]&&(anss==0||y<ans[anss]))
		{
			anss=++t;s[t]=y;//更新答案
			for(int i=1;i<=t;i++)
			ans[i]=s[i];
			t--;
			return true;
		}
		return false;
	}
	bool getans=false;//记录是否找到答案
	//下限,两者间取个最大值
	for(int i=maxx(s[t]+1,least(x,y));re*y>x*i;i++)//上限如上所述
	{
		int yy(y/gcd(y,i)*i);
		int xx(x*(yy/y)-yy/i);//xx/yy = x/y - 1/i
		s[++t]=i;//记录减去的分数的分母
		if(dfs(re-1,xx/gcd(xx,yy),yy/gcd(xx,yy)))getans=true;
		t--;
	}
	return getans;
}
#undef int

int main()
{
	#define int long long
	scanf("%lld %lld",&a,&b);
	int gg=gcd(a,b);
	a/=gg;b/=gg;
	if(a==1)printf("%lld",b),exit(0);
	s[0]=1;
	for(int i=2;;i++)
	{
		t=0;
		if(dfs(i,a,b))//如果找到答案就输出
		{
			for(int i=1;i<=anss;i++)
			printf("%lld ",ans[i]);
			return 0;
		}
	}
}

你可能感兴趣的:(题解_杂)