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

【题意】


        把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. 求最大公约数(好久没做过,忘个差不多了)。


猜你喜欢

转载自blog.csdn.net/disparity_cjk/article/details/53141825