【Vijos-P1308】埃及分数-迭代加深DFS+分数表示法

测试地址:埃及分数

做法:乍一看这道题,有点懵,因为不知道到底有多少个1/a才能组成这个分数,而BFS的空间耗费实在是太巨大了,因此我们就可以用迭代加深的思想来解决:在最外层枚举搜索的深度(即分数的个数),在这一层中搜索解,如果找不到解就继续加深,找到解则输出最优的解。而这题用浮点数存储分数的话会有各种奇怪的错误,所以我们可以用两个数a,b分别表示分数的分子和分母。在搜索时,我们记录前几层搜索后所剩下的分数la/lb(即a/b-1/x1-1/x2-...),设这一层所枚举的分母为i,则易发现以下不等式:1/i ≤ la/lb,(深度上限-已经取过的分数数量)*(1/i)>(la/lb),由于la,lb,i都是整数,所以:i≥lb/la,b*(深度上限-已经取过的分数数量)>a*i,这些就可以确定i的范围了。还需要注意,i还要大于上一层的分母,所以枚举时要从max(上一层分母+1,lb/la)开始搜索(要注意lb/la的计算)。那么,如何计算剩余数x/y=la/lb-1/i呢?就像我们小学学的一样通分即可:la/lb-1/i=(la*i)/(lb*i)-lb/(lb*i)=(la*i-lb)/(lb*i),所以x=(la*i-lb),y=(lb*i)。要注意约分,即x,y都除以gcd(x,y),这是为以下的剪枝做准备:如果搜到最后只剩一层,就无需枚举,查看剩余数的分子是否是1即可,这就体现出约分的重要性了。由于题目中要求最大分母最小,且与此同时分母的字典序要最小,由于我们枚举时就是按字典序查找的,所以我们不用考虑字典序的问题,而最大分母最小这一个条件很好判断,有更好解的时候直接替换即可。

以下是本人代码(别看上面讲了那么一堆,实际上程序不长...):

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 999999999
using namespace std;
long long a,b,depth,dion[101]={0},ans[101]={0};
bool flag=0;

long long gcd(long long a,long long b)
{
  return b==0?a:gcd(b,a%b);
}

void dfs(long long la,long long lb,long long step)
{
  if (step+1==depth)
  {
    if (la==1)
	{
	  dion[step+1]=lb;flag=1;
	  if (dion[step+1]<ans[step+1]||ans[step+1]==0)
	    memcpy(ans,dion,sizeof(dion));
	}
	return;
  }
  long long first=lb%la?lb/la+1:lb/la;
  for(long long i=max(dion[step]+1,first);;i++)
  {
    if (lb*(depth-step)<=i*la) break;
	dion[step+1]=i;
	long long nxta,nxtb,g;
	nxta=la*i-lb,nxtb=lb*i;
	g=gcd(nxta,nxtb);
	dfs((long long)nxta/g,(long long)nxtb/g,step+1);
  }
}

int main()
{
  scanf("%lld%lld",&a,&b);
  long long g=gcd(a,b);
  
  dion[0]=1;
  for(depth=1;;depth++)
  {
    dfs((long long)a/g,(long long)b/g,0);
	if (flag) break;
  }
  
  for(int i=1;i<=depth;i++)
    printf("%lld ",ans[i]);
  
  return 0;
}


posted @ 2016-10-05 10:34  Maxwei_wzj  阅读(97)  评论(0编辑  收藏  举报