迭代加深搜索

埃及分数问题。在古埃及,人们使用单位分数的和(即1/a,a是自然数)表示一切有理数。例如,2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为在加数中不允许有相同的。对于一个分数a/b,表示方法有很多种,其中加数少的比加数多的好,如果加数个数相同,则最小的分数越大越好。例如,19/45=1/5+1/6+1/18是最优方案。

输入整数 a,b(0<a<b<500) ,试编程计算最佳表达式。

样例输入:

495 499

样例输出:

Case 1: 495/499=1/2+1/5+1/6+1/8+1/3992+1/14970

  • 采用迭代加深搜索(iterative deepening):从小到大枚举深度上限maxd,每次执行只考虑深度不超过maxd的结点。这样,只要解的深度有限,则一定可以在有限时间内枚举到。

  • 对于可以用回溯法求解但解答树的深度没有明显上限的题目,可以考虑使用迭代加深搜索(iterative deepening)。

  • 深度上限maxd还可以用来“剪枝”。按照分母递增的顺序来进行扩展,如果扩展到i层时,前i个分数之和为c/d,而第i个分数为1/e,则接下来至少还需要(a/b-c/d)/(1/e)个分数,总和才能达到a/b。例如,当前搜索到19/45=1/5+1/100+…,则后面的分数每个最大为1/101,至少需要(19/45-1/5) / (1/101) =23项总和才能达到19/45,因此前22次迭代是根本不会考虑这棵子树的。这里的关键在于:可以估计至少还要多少步才能出解。

  • 注意,这里的估计都是乐观的,因为用了“至少”这个词。说得学术一点,设深度上限为maxd,当前结点n的深度为g(n),乐观估价函数为h(n),则当g(n)+h(n)>maxd时应该剪枝。这样的算法就是IDA*。当然,在实战中不需要严格地在代码里写出g(n)和h(n),只需要像刚才那样设计出乐观估价函数,想清楚在什么情况下不可能在当前的深度限制下出解即可。

  • 如果可以设计出一个乐观估价函数,预测从当前结点至少还需要扩展几层结点才有可能得到解,则迭代加深搜索变成了IDA*算法。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;
const int maxNum = 100 + 5;
LL a, b, maxd;
// 当前解和最终解
LL v[maxNum], ans[maxNum];

// 返回满足 1/c <= a/b 的最小c
inline int get_first(LL a, LL b) {
    return b / a + 1;
}

// 欧几里得算法
LL gcd(LL a, LL b) {
    return b == 0 ? a : gcd(b, a % b);
}

// 如果当前解v比最优解ans更优,更新ans
bool better(int d) {
    for(int i = d; i >= 0; i--) {
        if(v[i] != ans[i]) {
            return ans[i] == -1 || v[i] < ans[i];
        }
    }
    return false;
}

// 当前深度为d,分母不能小于from,分数之和恰好为aa/bb
bool dfs(int d, int from, LL aa, LL bb) {
    if(d == maxd) {
        // aa/bb必须是埃及分数
        if(bb % aa) {
            return false;
        }
        // 深度为d时的分母
        v[d] = bb / aa;
        if(better(d)) {
            memcpy(ans, v, sizeof(LL) * (d + 1));
        }
        return true;
    }
    bool ok = false;
    // 枚举的起点
    from = max(from, get_first(aa, bb));
    for(int i = from; ; i++) {
        // 剪枝:如果剩下的max+1-d个分数全部都是1/i,
        // 加起来仍然不超过aa/bb,则无解
        if(bb * (maxd + 1 - d) <= i * aa) {
            break;
        }
        v[d] = i;

        // 计算aa/bb - 1/i,设结果为a2/b2
        LL b2 = bb * i;
        LL a2 = aa * i - bb;
        // 获取最大公约数
        LL g = gcd(a2, b2);
        if(dfs(d + 1, i + 1, a2 / g, b2 / g)) {
            ok = true;
        }
    }
    return ok;
}

int main() {
    int kase = 0;
    while(cin >> a >> b) {
        int ok = 1;
        // 迭代加深
        for(maxd = 1; maxd <= 100; maxd++) {
            memset(ans, -1, sizeof(ans));
            if(dfs(0, get_first(a, b), a, b)) {
                ok = 1;
                break;
            }
        }
        cout << "Case " << ++kase << ": ";
        if(ok) {
            cout << a << "/" << b << "=";
            for(int i = 0; i < maxd; i++) {
                cout << "1/" << ans[i] << "+";
            }
            cout << "1/" << ans[maxd] << endl;
        } else {
            cout << "No solution." << endl;
        }
    }
    return 0;
}

你可能感兴趣的:(ACM,迭代加深搜索)