Loading

埃及分数问题——迭代加深搜索

埃及分数问题

在古埃及,人们使用单位分数的和(即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),试编程计算最佳表达式。

思路

此题也没有个什么明确的上界和下界,不好用回溯或者什么其他办法解决。

想想如果使用回溯之类的深度优先搜索,每次添加一个分数,直到找到等于a/b的组合,因为分母想多大就能多大,所以解答树根本就是无限高的,根本不知道什么时候停下,这时候完成一次深度搜索都是奢望。简而言之就是算法会在一条分支上跑到死。

这时可以考虑给深度优先搜索加一个最大深度,这样算法不会在一条分支上一直跑,到达最大深度之后判断是否满足答案,如果满足就更新答案,如果不满足就跑去再执行其他分支。当这解答树为这个深度并且遍历了所有的可能情况后还是没有出现期望的答案,就把最大深度加1,再次搜索。

代码

这个代码还是不太好理解,其实代码和思想都不难,关键这题有点让人看到就恶心。。。

#include "iostream"
#include "cstdio"

#define MAX 1000

using namespace std;
typedef long long LL;
int best_ans[MAX], ans[MAX];
int a, b;
int maxd;

/*
寻找一个最小的c,使得1/c<=a/b
c*a>=b 
*/
int first(int a, int b) {
    int c = 1;
    while (c * a < b)c++;
    return c;
}

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

int better(int d) {
    for (; d >= 0; d--) 
        if (ans[d] != best_ans[d]) 
            return best_ans[d] == -1 || ans[d] < best_ans[d];
    return false;
}

/*
d      当前深度
from   当前序列中最后一个分母,后面的所有分母不能小于这个
aa/bb  剩余的a/b
*/
bool dfs(int d, int from, LL aa, LL bb) { 
    if (d == maxd) {
        if (bb % aa != 0) return false; // 如果b % a不是0,则说明最后一个不是1/xx的形式
        ans[d] = bb / aa;
        // 如果这个答案比已有答案更好,就更新
		if (better(d))memcpy(best_ans, ans, sizeof(ans));
        return true;
    }

    bool ok = false;
    // 选择后一个数的起始位置,from做直接做分母加到后面可能超过a/b
    from = max(from, first(aa, bb));
    for (;; from++) {
        /*
        对于最大深度为maxd,当前深度为d的搜索,最多后面还能接(maxd-d+1)个数
        最大情况下,后面的数都是1/from
        如果(maxd-d+1)*1/from < aa/bb,证明后面无法凑出aa/bb,做了工作也是徒劳,可以剪枝了
        */
        if (bb * (maxd - d + 1) <= aa * from)break; 
        ans[d] = from;
        /*
        加上1/from下面剩余的数就是 aa/bb-1/from
        通分得如下公式
        */
        LL bb2 = bb * from;
        LL aa2 = aa * from - bb;
        int g = gcd(aa2, bb2); // 通分
        if (dfs(d + 1, from + 1, aa2 / g, bb2 / g))ok = true;
    }
    return ok;
}
void print_ans() {
    printf("%d/%d=", a, b);
    for (int i = 0;; i++) {
        if (best_ans[i] < 0)break;
        printf("1/%d ", best_ans[i]);
    }
    printf("\n");
}

int main() {
    while (scanf("%d %d", &a,&b) != EOF) {
        for (maxd = 1;; maxd++) {
            memset(best_ans, -1, sizeof(best_ans));
            memset(ans, -1, sizeof(ans));   
            if (dfs(0, first(a, b), a, b)) {
                print_ans(); break;
            }
        }
    }
    return 0;
}

IDA*和启发函数

IDA*是结合了迭代加深和A*算法优点的算法,A*算法就是加了一个启发函数来剪枝的bfs算法。

上面的算法就是一个IDA*算法,具体的看紫书吧~~

posted @ 2020-12-05 15:59  yudoge  阅读(299)  评论(0编辑  收藏  举报