UESTC Training for Search Algorithm——D

分数拆分

Description

任何一个分数都能才成若干个单位分数(形如1/a的, a是自然数)的和。

对于一个分数a/b,表示方法有很多种,但是哪种最好呢?

首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好,如果还是相同,那么第二小的分数越大越好,依次类推下去。

例如对于19/45,下列方法都是合法的:

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),求最好的表达方式。

Input

第一行有一个整数T,表示有T(T<=50)组测试数据,每组测试数据为一行包含a,b(0<a<b<1000)。

Output

每组测试数据若干个数,自小到大排列,依次是单位分数的分母。

Sample Input

1
19 45

Sample Output

5 6 18

 

/*算法思想:
  刚开始的时候毫无头绪,听了讲座之后顿时大悟,用的老师讲的方法,
  暴力DFS,判断当前的分数是否可行,可行的条件是:当前的分数比上
  一个用过的分数小,并且假设后面所有的分数都取这个分数,求和一定
  要比剩下的待合成的分数大,直到找到解,然后与作为answer的解序列
  比较,取最优的,要是这个深度已经能够找到解了,不进行下一个深度的
  搜索了,最后输出结果,由于怕数据太大,用的是long long保存的,剪枝的话
  小剪枝,本次搜索的这个位子上的分数的分母要比作为answer的对应位子的分母小
*/

/*CODE*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100
using namespace std;
long long ans[101],now[101];
long long gcd(long long a,long long b)  //求两个数的最大公约数
{
    if(b==0) return a;
    return gcd(b,a%b);
}
bool big(long long *a,long long *b)   //判断a数组中的解序列是不是比b数组中的解序列更优
{
    for(long long i=a[0];i>0;i--)
    {
        if(a[i]>b[i]) return true;
        if(a[i]<b[i]) return false;
    }
    return true;
}
void cmp()  //比较现在的解序列和作为answer的解序列谁更优
{
    if(ans[0]==0)
    {
        for(long long i=0;i<=now[0];i++)
            ans[i]=now[i];
        return;
    }
    if(ans[0]<now[0]) return;
    if(big(ans,now))
    {
        for(long long i=0;i<=ans[0];i++)
            ans[i]=now[i];
    }
}
/*DFS,搜索的深度,还剩下的分数的大小是: b/a 上次用的是 1/c 的单位分数*/
void dfs(long long deep,long long b,long long a,long long c)
{
    if(now[0]>deep) return;  //超过了枚举深度
    if(b==1 && a>c)  //可以直接找到解
    {
        now[++now[0]]=a;
        cmp();
        --now[0];
        return;
    }
    long long down;  //下界
    if(a%b==0) down=a/b;
    else down=a/b+1;
    if(down<=c) down=c+1;
    long long up=a*(deep-now[0])/b;  //上界
    if(ans[0]!=0 && up>ans[now[0]+1]) up=ans[now[0]+1];  //小优化,这次的这个位子上的分数的分母要比作为answer的对应位子的分母小
    for(long long k=down;k<=up;k++)
    {
        now[++now[0]]=k;
        long long p=gcd(b*k-a,a*k);
        dfs(deep,(b*k-a)/p,a*k/p,k);
        --now[0];
    }
}
int main()
{
    //freopen("C:/Users/hp-john/Desktop/out.txt","w",stdout);
    long long t,a,b;
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld%lld",&b,&a);
        memset(ans,0,sizeof(ans));
        memset(now,0,sizeof(now));
        for(long long i=1;i<=100;i++)
        {
            dfs(i,b/gcd(a,b),a/gcd(a,b),0);
            if(ans[0]>0)
            {
                for(long long i=1;i<ans[0];i++) printf("%lld ",ans[i]);
                printf("%lld ",ans[ans[0]]);
                break;
            }
        }
        printf("\n");
    }
    return 0;
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值