習題7-7:埃及分數(迭代加深)

習題7-7:埃及分數
題意:
對一個分數(n/m)將它分解成若干個不相等的單分子分數(即分子爲1)。求最少能分解成哪幾個分數相加。若有多解,輸出最大的分母儘量的小的解。會有k個禁止使用的單分子分數。
思路:
這題搜索的特點就是深度和寬度都不確定,首先對於一個a/b,你不知道他的最優解有幾個1/a類似形式的分數構成,這是寬度。對於每個1/i他的深度也是不確定的,唯一剪枝的條件就是確定深度後餘下都選擇1/i之和小於a/b。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<sstream>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
int dir[4][2] = {1,0,0,1,-1,0,0,-1};
int dir2[8][2] = {1,1,1,-1,-1,1,-1,-1,1,0,0,1,-1,0,0,-1};
const int inf = 1 << 30;
const int maxn = 1e5 + 10;
const double eps = 1e-8;
ll v[maxn], ans[maxn];
ll maxd;
set<ll>q;
ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b , a % b);
}
ll getfirst(ll a, ll b)
{
    for(ll i = 2; ;i++)
    {
        if(q.count(i))continue;///如果是有分母爲禁止的分母,continue
        if(b < a * i)return i;
    }
}
bool better(ll d)
{
    for(ll 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(ll d, ll from, ll aa, ll bb)
{
    if(d == maxd)
    {
        if(aa != 1)return false;///必須是埃及分數
        if(q.count(bb))return false;///如果是有分母爲禁止的分母,返回假
        v[d] = bb;
        if(better(d))memcpy(ans, v, sizeof(ll) * (d + 1));
        return true;
    }
    bool ok = false;
    from = max(from, getfirst(aa, bb));///枚舉起點,去上一次加一的分母值和比a/b小的最大分數的分母中更大的。
    for(ll i = from; ; i++)
    {
        if(q.count(i))continue;///如果是有分母爲禁止的分母,continue
        ///剪枝,如果c/d(前i個分數之和)+1/e*(maxd-i)<a/b,可以直接break;
        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()
{
    ll a, b, c, x;
    int t, cases = 0;
    cin >> t;
    while(t--)
    {
        q.clear();
        cin >> a >> b >> c;
        while(c--)
        {
            cin >> x;
            q.insert(x);
        }
        for(maxd = 1;;maxd++)
        {
            memset(ans, -1, sizeof(ans));
            if(dfs(0, getfirst(a, b), a, b))break;
        }
        printf("Case %d: %lld/%lld=",++cases, a, b);
        for(ll i = 0; i <= maxd; i++)
        {
            if(i)cout<<"+";
            cout<<"1/"<<ans[i];
        }
        cout<<endl;
    }
    return 0;
}
///這個程序是反向搜索,
///59 211 答案有兩個爲
///4 36 633 3798 和 6 9 633 3798,反向搜索的話輸出第一組數據,正向輸出第二組
/*#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<sstream>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
int dir[4][2] = {1,0,0,1,-1,0,0,-1};
int dir2[8][2] = {1,1,1,-1,-1,1,-1,-1,1,0,0,1,-1,0,0,-1};
const int inf = 1 << 30;
const int maxn = 1e5 + 10;
const double eps = 1e-8;
ll v[maxn], ans[maxn];
ll maxd;
ll gcd(ll a, ll b)
{
    return b == 0 ? a : gcd(b , a % b);
}
///還有d層,分母不能小於from,分數之和爲aa/bb
bool dfs(ll d, ll from, ll aa, ll bb)
{
    if(aa < 0)return false;
    if(d == 1)
    {
        if(aa != 1)return false;///必須是埃及分數
        if(bb < from)return false;
        if(bb < ans[1])
        {
            ans[1] = bb;
            for(int i = maxd; i >= 2; i--)ans[i] = v[i];
        }
        return true;
    }
    bool ok = false;
    for(ll i = from; ; i++)
    {
        ///剪枝,如果c/d(前i個分數之和)+1/e*(maxd-i)<a/b,可以直接break;
        if(bb * 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()
{
    ll a, b, aa, bb;
    cin >> a >> b;
    ll g = gcd(a, b);
    aa = a / g;
    bb = b / g;
    if(aa == 1)cout<<bb<<endl;
    else
    {
        for(maxd = 1;;maxd++)
        {
            ans[1] = 1e17;
            if(dfs(maxd, 1, aa, bb))break;
        }
        for(ll i = maxd; i; i--)
        {
            cout<<ans[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章