習題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;
}*/