uva12558 埃及分数 IDDFS

uva 12558

题目:

Given a fraction a/b, write it as a sum of different Egyptian fraction. For example, 2/3 = 1/2 + 1/6.
There is one restriction though: there are k restricted integers that should not be used as a denominator.
For example, if we can’t use 2..6, the best solution is:
2/3 = 1/7 + 1/9 + 1/10 + 1/12 + 1/14 + 1/15 + 1/18 + 1/28
The number of terms should be minimized, and then the large denominator should be minimized.
If there are several solutions, the second largest denominator should be minimized etc.
Input
The first line contains the number of test cases T (T ≤ 100). Each test case begins with three integers
a, b, k (2 ≤ a < b ≤ 876, 0 ≤ k ≤ 5, gcd(a, b) = 1). The next line contains k different positive integers
not greater than 1000.
Output
For each test case, print the optimal solution, formatted as below.
Extremely Important Notes
It’s not difficult to see some inputs are harder than others. For example, these inputs are very hard
input for every program I have:
596/829=1/2+1/5+1/54+1/4145+1/7461+1/22383
265/743=1/3+1/44+1/2972+1/4458+1/24519
181/797=1/7+1/12+1/2391+1/3188+1/5579
616/863=1/2+1/5+1/80+1/863+1/13808+1/17260
22/811=1/60+1/100+1/2433+1/20275
732/733=1/2+1/3+1/7+1/45+1/7330+1/20524+1/26388
However, I don’t want to give up this problem due to those hard inputs, so I’d like to restrict the
input to “easier” inputs only. I know that it’s not a perfect problem, but it’s true that you can still
have fun and learn something, isn’t it?
Some tips:
1. Watch out for floating-point errors if you use double to store intermediate result. We didn’t use
double.
2. Watch out for arithmetic overflows if you use integers to store intermediate result. We carefully
checked our programs for that.
Sample Input
5
2 3 0
19 45 0
2 3 1 2
5 121 0
5 121 1 33
Sample Output
Case 1: 2/3=1/2+1/6
Case 2: 19/45=1/5+1/6+1/18
Case 3: 2/3=1/3+1/4+1/12
Case 4: 5/121=1/33+1/121+1/363
Case 5: 5/121=1/45+1/55+1/1089

题意:

题意就是求埃及分数,给你一个真分数a/b,让你把它拆分为一组分子为1的分数1/k i。多种可行解中,先选项数最小的,如果一样多,比较两组中分母最大的,选小的那个,一样多就在比较两组中第二大的,依次类推,求出最优解。

解法:

首先我们想这个题该如何表示状态:
1. 每个节点表示读到第几个数字的状态,两个子树表示选与不选。然后就会发现因为分母即使在项数很小的情况下也可能很大,而且不只穷尽,状态多到爆炸。
2. 我们可以换一种思路,每一个节点表示的是选到第几项时的状态,然后就发现,每个节点的度数是无穷。。。。

所以,我们选择基于第二种思想用迭代加深的方法(IDDFS),就是每次搜索的时候对于项数设置一个limit值,搜索层数到了limit值判断是否满足条件,如果多个就按规则选择最优的,如果没有就把limit加大再重新搜索一遍。这时应该一些人会想这得走重多少路啊,但是其实这些对于复杂度指数级别的搜索指数加了应该常数而已。相反限制了树的深度和度数,时间缩短更明显。顺便说一下如何用limit值解决思路(2)的问题,limit存在后,项数已经确定,那么分母对于某一项分母k一定满足k<=(limit-cur)*b/a,(limit-cur)表示的是还有多少项每选。

代码就是与一般搜索差得就是多了个limit,对于一个limit,因为之前项数小时都搜索过,没有可行解,所以统一在cur==limit-1时判断是否可行,选出来的就是项数最小的解。
每一个节点找子节点(分母k),下界是max(last+1,b/a),last表示上一次取的数字,因为一个数只能选一次,又是从小到大选。1/k

代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cmath>
#define INF 0x7fffffff
using namespace std;
int error[8];
int res[20000];
int temp[2000];
int a,b,k;
int gcd(int a,int b)
{
    return (b? gcd(b,a%b):a);
}
//cur是已经取了多少个,limit限制,a,b就是分子,分母,last是上一个分母。
bool IDDFS(int cur,int limit,int a,int b,int last)
{
    if(cur==0)memset(temp,0,sizeof(temp));
    if(cur==limit-1)
    {
        if(a!=1||b<=last)
            return false;
        for(int i=1;i<=k;i++)
            if(b==error[i])return false;
        temp[limit]=b;
        if(res[1])
        {
            for(int i=limit;i>=1;i--)
            {
                if(temp[i]<res[i])
                {
                    for(int j=1;j<=limit;j++)
                        res[j]=temp[j];
                    break;
                }
                else if(temp[i]>res[i])
                    break;
            }
            return true;
        }
        else
        {
            for(int j=1;j<=limit;j++)
                res[j]=temp[j];
            return true;
        }
    }
    if(a==0)return false;
    bool flag=false;
    for(int  i=max(last+1,b/a) ;i<=INF/b&&i<=(limit-cur)*b/a;i++)
    {
        bool check=false;//判断check是否在error中
        for(int j=1;j<=k;j++)
            if(error[j]==i)
                check=true;
        if(check==true)
            continue;
        int a1=a*i-b,b1=b*i;
        int x=gcd(a1,b1);
        a1/=x,b1/=x;
        temp[cur+1]=i;
        if(IDDFS(cur+1,limit,a1,b1,i))flag=true;
    }
    return flag;

}

int main()
{
    int T;
    int cases=0;
    scanf("%d",&T);
    while(T--)
    {
        memset(res,0,sizeof(res));
        memset(temp,0,sizeof(temp));
        scanf("%d%d%d",&a,&b,&k);
        for(int i=1;i<=k;i++)
            scanf("%d",&error[i]);
        int x=1;
        while(1)
            if(IDDFS(0,x,a,b,1))  break;
            else x++;

        printf("Case %d: %d/%d=1/%d",++cases,a,b,res[1] );
        for(int i=2;i<=x;i++)
            printf("+1/%d", res[i]);
        printf("\n");
    }
    return 0;
}

P.S.刚开始看这个一脸懵逼,读了一个巨巨的文章,顿时醍醐灌顶,先膜为敬。
http://blog.csdn.net/abc13068938939/article/details/52173953

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值