EOJ 1154/ UVA 12558 埃及分数问题 迭代加深搜索

我们称呼分子为1的分数为埃及分数

对于把一个分数拆解成n个埃及分数相加的形式的方法肯定是多种多样的,例如 59/211 = 1/4 + 1/36 + 1/633 + 1/3798 = 1/6 + 1/9 + 1/633 + 1/3798.那么我们会选择哪种呢?

partychen告诉我们:

1.分解所需要的数越少越好

2.当1相同时,最后一个分数越大越好,即分母越小越好

3.当1,2相同时,这些分数的分母字典序越小越好。

那么现在就来思考算法了。很容易想到的思路。

现在有一个分数a/b。小于等于a/b的埃及分数有1/k, 1/(k+1), 1/(k+2).....

我们先将1/k拆分出去,然后继续拆分a/b - 1/k,如果我们发现这条路是行不通的。我们就试试先将1/(k+1)拆分出去,然后继续拆分a/b - 1/(k+2)。如果我们发现这条路还是行不通的,就换1/(k+3)........总是能找到答案的。

即拆分a/b,我们先尝试第一个小于等于a/b的埃及分数,再尝试第二个小于等于a/b的埃及分数,依次类推, 直到原分数拆分成0为止。

这种思路不就是dfs吗,它的解答树正好是按照深度优先的规则来遍历的。

想到这里我们就可以很高兴的写dfs了,但是写出来肯定会发现不对劲的。在写dfs之前我们应该先估计一下解答树的深度上限,不然有可能会爆栈。

对于这道题,我们每次都选择第一个小于它的埃及分数来拆分。首先我们要明白,任何一个分数总是能够找到第一个小于它的埃及分数的,换句话说解答树中的每个节点都有子节点。即解答树的深度是无穷大的。

如果我们陷入到了一种很坏的情况,虽然不至于搜索到无穷大,也会搜索到很深很深。这样是不可行的。

现在的问题在哪?dfs我们会搜索到很深。也就是解答树的深度没有明显的上界。这时候就要引入一种新的搜索方法,我们叫它迭代加深搜索。

迭代加深搜索和dfs基本相同,只不过我们限制了它的搜索的最大深度。假如一个分数最好的方案是拆解成三个埃及分数相加,其实也就是dfs我们要搜索三层。

那么按照迭代加深的思路,就是我们先限制搜索层数是0层,无解。1层,无解。2层,无解。3层,有解。只要这个解是存在的,通过深度不断增加的dfs,我们肯定可以找到它。

迭代加深有什么好处呢?

1.避免了因为深度没有明显上界,使得搜索很慢很慢的情况。

2.继承了dfs耗费空间少的特点。

3.因为我们设置了深度上限,在得知我们当前深度的情况下,我们可以利用已知信息剪枝。举个不恰当的例子,假如老师叫我们三天写1000道题目。我们有多种方法可以完成这个任务。按照a方法到了第二天晚上的时候,我们只做了50道。根据已有的上界:3天1000道题目,和我们当前的深度:直到第二天晚上只做了50道题目,还需要的任务量:6个小时做950道题目。我们就可以得出结论:这任务按照你之前的速度没法完成,要想完成老师的任务你要试试其他方法。比如我们可以试试b方法:直接copy同学作业,哈哈。

还有不懂的地方可以看一下网上关于迭代加深的讲解。代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long int LL;
const LL INF = 0x3f3f3f3f;
const LL maxn = 1005;
LL maxd;//maxd表示当前我们限制的深度上限
LL ANS[maxn], ans[maxn];

LL gcd(LL x, LL y){return y == 0 ? x : gcd(y, x % y);}

bool is_better()//判断现在的答案是不是比之前的答案更优
{
    if (ans[maxd] != ANS[maxd]) return ans[maxd] < ANS[maxd];
    bool res = true;
    for (int i = 0; i <= maxd - 1; i++){
        if (ans[i] > ANS[i]){
            res = false;
            break;
        }
    }
    return res;
}

bool dfs(LL a, LL b, LL level, LL d){
    LL gc = gcd(a, b);
    a = a / gc;
    b = b / gc;

    if (d == maxd){
        if (a == 1 && b >= level){
            ans[d] = b;
            if (is_better()) memcpy(ANS, ans, sizeof(ans));
            return true;
        }
        return false;
    }

    bool res = false;
    for (int i = level; ; i++){
        if ((maxd - d + 1) * b <= a * i) break;//因为我们拆分用到的埃及分数是越来越小的,也就是在之后的遍历中我们使用的分数是
        //小于1/i的,现在假设最好情况,每次我们都用1/i。结果发现就算我们每次都用最大的,也无法把已知分数拆分成0.这还有什么继续的必要
        //呢?所以不用再搜索了,剪枝!

        if (a * i >= b){// 1/i <= a/b 可以用来拆分 
            ans[d] = i;
            if (dfs(a * i - b, b * i, i + 1, d + 1)) res = true;
        }
    }

    return res;
}

int main()
{
    //freopen("1.txt", "r", stdin);
    LL a, b;
    while (~scanf("%I64d%I64d", &a, &b)){
        memset(ANS, INF, sizeof(ANS));
        for (maxd = 0; ; maxd++){//深度上限递增,我们总能找到答案
            if (dfs(a, b, 1, 0))
                break;
        }

        for (int i = 0; i <= maxd; i++)
            printf("%I64d%c", ANS[i], i < maxd ? ' ' : '\n');
    }
    return 0;
}


你可能感兴趣的:(ACM-搜索)