UVA 12558 埃及分数(迭代搜索)

来源:互联网 发布:mac 安装oracle 编辑:程序博客网 时间:2024/06/07 08:11

【题意】


        把a/b写成不同分数之和,且分数分子必须为1,要求项数尽量小,在此前提下最小的分数尽量大,然后第

    二小的分数尽量大……另外有k(0<=k<=5)个不超过1000的正整数不能用作分母。

            输入保证2<=a<b<=876,gcd(a,b)=1。



【分析】


       通过之前看的简单的埃及分数,知道了大体的求解步骤。

       1.  此题需要用迭代加深搜索,如果只dfs,是一个没有下界的搜索,死递归;如果bfs,内存开销大。

       2.  选当前分数时,从最接近的值开始,a/b最接近的值就是1/(b/a),然后是结束,结束最大为剩余步数

  step*b/a,因为1/(b/a)平均拆成step份,step个分数相加才等于a/b,因为越往后分母越大,如果当前分母都

  比step*b/a大,无论后面是多大的分数加和都小于a/b。

       3.  将不能出现的正整数用数组标记下来,遍历时进行判断。因为给你不能出现的正整数范围比较小,所以

  用数组标记节约时间,如果数据范围大,可以选择用set容器进行存储,进行判断是否当前正整数能使用。使用

  数组标记就要注意一个细节,就是正整数不超过1000,所以判断时,先判断是否超过1000,在判断数组,不然

  会数组越界。

        4.  因为分母会很大,int是存不下的,需要用long long。简单的埃及分数是使用的int。 

        5.  拆分后的分数项数,没有具体要求所以,最好定大一些,不过此题不用太大,反正10项可以过。

 

【代码】

#include<stdio.h>#include<string.h>const int N=10;typedef long long LL;LL dep,flag,pre[N],now[N];bool book[1010];// 函数功能:求最大公约数LL gcd(LL a,LL b){    LL temp;    while(a!=0)    {        temp=a;        a=b%a;        b=temp;    }    return b;}// 函数功能:遍历第dep步的所有解void dfs(LL a,LL b,LL k){    if(b%a==0&&b/a>now[k-1]&&(b/a>1000||!book[b/a])) // 找到符合要求的结果    {           /* 不要忘记判断最后的结果是否能使用,不然会WA,且要记得b/a的范围在1000以内才能判断,不然会数组越界 */        /* 不能把book放下面判断,没有循环continue不能用,return会出错,可能没有到达dep步b%a==0,但是b/a是不能使用的 */        now[k]=b/a;        bool ans=0;        for(int i=k;i>=1;i--)        {            if(now[i]<pre[i])            {                ans=1;                break;            }            else if(now[i]>pre[i])                break;        }        if(!flag||ans)            memcpy(pre,now,sizeof(now));        flag=1;        return ;    }    LL s=b/a;    if(s<=now[k-1]) s=now[k-1]+1;    LL t=(dep-k+1)*b/a;   // 迭代搜索执行到第dep步就结束了,限制上界                          /* 之所以是这个公式是,s是使等式成立最接近的解,把s平均拆分成dep-k+1份,如果没t还小,剩下的dep-k步无论取多少都会偏小  */    if(flag&&t>pre[dep]) t=pre[dep]-1;    for(LL i=s;i<=t;i++)    {        if(i<=1000&&book[i]) // 判断这个点能否使用,不要忘记范围,不要越界访问            continue;        now[k]=i;        LL m=gcd(a*i-b,b*i);        dfs((a*i-b)/m,(b*i)/m,k+1);    }    return ;}// 函数作用:简洁。可去掉,放在main函数中void slove(LL a,LL b){    now[0]=1;    for(dep=2;dep<=N;dep++)    {        dfs(a,b,1);        if(flag)        {            printf("1/%lld",pre[1]);            for(LL i=2;i<=dep;i++)                printf("+1/%lld",pre[i]);            printf("\n");            return ;        }    }    return ;}int main(){    int T,cnt=1;    scanf("%d",&T);    while(T--)    {        flag=0;        memset(book,false,sizeof(book));        LL a,b,k,x;        scanf("%lld %lld %lld",&a,&b,&k);        while(k--)        {            scanf("%lld",&x);            book[x]=true;        }        printf("Case %d: %lld/%lld=",cnt++,a,b);        slove(a,b);    }    return 0;}

【收获】


       1. 认真思考题目的上界是什么。在题目没有给上界或下界时,需要自己去推出

上界(下界),如果推不出来就尽量开大一些。如果没有上界,递归就是死递归。

       2. 注意细节。使用数组时,要注意思考下标是否会越界,此题RE了n次。

       3. 思考问题,要认真思考。此题结束条件是一个坑,因为有不能使用的正整

数,所以可能没有走到对应的步数或者走到对应步数的正整数不能使用,如果处理

不好就会WA。认真思考结束条件和特殊数据

       4. long long 的使用。

       5. 求最大公约数(好久没做过,忘个差不多了)。


0 0
原创粉丝点击