1. 程式人生 > 實用技巧 >一些列舉題目(二)

一些列舉題目(二)

埃及分數(Eg[y]ptian Fractions (HARD version), Rujia Liu's Present 6, UVa12558)

把a/b寫成不同的埃及分數之和,要求項數儘量小,在此前提下最小的分數儘量大,然後第二小的分數儘量大……另外有k(0≤k≤5)個數不能用作分母。例如,k=0時\(5/121=1/33+1/121+1/363\),不能使用33時最優解為\(5/121=1/45+1/55+1/1089\)。輸入保證\(2≤a<b≤876,gcd(a,b)=1\),且會挑選比較容易求解的資料。

輸入輸出

第一行輸入是T,下面T行每行是一組輸入示例,每行的前三個數是a,b,k。之後的k個數是不能用作分母的k個數。

Sample Input
5
2  3  0
19  45  0
2  3  1  2
5  121  0
5  121  1  3

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

思路

之前做埃及分數的時候,由於這是紫書裡介紹迭代加深的第一道題,在當時不瞭解迭代加深的原理再加上這題這麼噁心的情況下,沒咋明白。這回明白了。

思路詳見程式碼註釋

程式碼

#include "iostream"
#include "cstdio"
#include "cstring"
#include "cmath"
#include "set"
#define LL long long int
#define MAXK 5
#define MAXCS 100

using namespace std;
LL a, b,k,cs[MAXCS],ans[MAXCS];
set<int> ks; int cnt;

LL gcd(LL a, LL b) {
    return b == 0 ? a : gcd(b, a % b);
}
/*
找到第一個滿足1/c<a/b的c

1/c<a/b
c<b/a
所以c為b/a向上取整或者b/a+1
*/
LL first_smaller_than(LL aa, LL bb) {
    return ceil(bb / (double)aa);
}
/*
因為題目中要求的是找到項數儘量小,最小的分數儘量大,第二小的分數儘量大...
所以先找到的答案可不一定是正確的,還得在此深度下繼續查詢

better判斷當前的答案是否比已有答案更好
*/
bool better(int maxd) {
    for (int i = maxd ; i >= 0; i--) 
        if (cs[i] != ans[i])return ans[i]==-1||cs[i] < ans[i];
    return false;
}

/*
之前選擇的是選擇第d個1/c
最大選擇maxd個1/c
當前離總目標a/b還差aa/bb
*/
bool dfs(int d,int maxd,LL first,LL aa, LL bb) {
    // 如果深度等於最大深度
    if (d == maxd) {
        // 如果最後一個滿足1/x的形式並且不在ks中
        if (bb % aa || ks.count(bb / aa))return false;
        cs[d] = bb / aa;
        // 如比當前答案更好 更新
        if (better(maxd)) {
            memcpy(ans, cs, sizeof(cs));
            return true;
        }
        return false;
    }

    first = max(first,first_smaller_than(aa,bb));
    bool ok = false;
    // 往後依次遍歷每一個c
    for (int c = first;; c++) {
        // 如果c在ks裡,不要
        if (ks.count(c) != 0)continue;
        // 如果後面的有限個都用1/c還是湊不夠aa/bb,剪枝
        if (bb*(maxd+1-d)<=aa*c) 
            break;
        cs[d] = c;  
        // 算新的aabb,並通分
        LL naa=aa*c-bb, nbb = bb*c;
        LL g = gcd(nbb, naa);
        if (dfs(d + 1, maxd,c+1, naa/g, nbb/g))ok = true;
    }
    return ok;
}

int main() {
   int T;
   scanf("%d", &T);
   for (int kase = 1; kase <= T; kase++) {
       scanf("%lld %lld %d", &a, &b, &k);
       ks.clear();
       for (int i = 0; i < k; i++) { int tmp; scanf("%d", &tmp); ks.insert(tmp); }
        for (int maxd = 1;; maxd++) {
            memset(ans, -1, sizeof(ans));
            cnt = 0;
            if (dfs(0, maxd,first_smaller_than(a,b), a, b)) {
                printf("Case %d: %lld/%lld=", kase, a, b);
                for (int j = 0; j <= maxd; j++) {
                    printf("1/%lld", ans[j]);
                    if (j < maxd)printf("+");
                }
                printf("\n");
                break;
            }
        }
   }
    return 0;
}