1. 程式人生 > 實用技巧 >埃及分數問題——迭代加深搜尋

埃及分數問題——迭代加深搜尋

埃及分數問題

在古埃及,人們使用單位分數的和(即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*演算法,具體的看紫書吧~~