一、题意
埃及分数问题。
给一个分数a/b。
将它拆成式子:a/b=1/c1+1/c2+1/c3...
其中c1,c2,c3,...严格递增,且尽可能使得项数最少,在此前提下最大的c值最小。
输出式子。
二、解析
一般来说枚举题理论上可以用回溯法解决,但是在这题中,我们可以发现回溯法每一层的枚举数量为无限...这个时候我们就要使用迭代加深搜的方法了,或者说使用IDA*算法。
也就是枚举深搜的最大深度maxd,然后在maxd的限制下去进行dfs,并利用maxd来对dfs进行剪枝,使得dfs的每一层可以很快枚举完。
每次针对一个maxd进行dfs时,实际上就是相当于反复去解答这道题,因此也要重新初始化。
由于需要由maxd限制递归层数,因此dfs函数的参数必须包括一个idx表示当前递归的层数,在这题中就是当前正在考虑的式子中的第几项。
剩下的参数就按照需要,比如这题中需要维护剩余分数和,也即a和b的值,然后为了使得下一层枚举的初始值c要比上一层大,还需要增加另一个参数prev_c。
三、代码
#include <iostream> #include <vector> #include <cstring> #include <algorithm> using namespace std; using LL = long long; const LL maxn = 1000; LL kase, a, b, k, ans[maxn], v[maxn]; vector<LL> forbid; LL gcd(LL a, LL b) { return b == 0 ? a : gcd(b, a % b); } bool isBetter(LL n) { LL i = n - 1; for(; i >= 0; i --) if(v[i] != ans[i]) break; return ans[i] == -1 || v[i] < ans[i]; } bool dfs(int maxd, int idx, LL a, LL b, LL prev_c) { if(idx == maxd) { if(a) return 0; if(isBetter(idx)) memcpy(ans, v, sizeof(LL) * idx); return 1; } bool ok = 0; for(LL c = max(prev_c + 1, (b % a ? b / a + 1 : b / a)); ; c ++) { if(find(forbid.begin(), forbid.end(), c) != forbid.end()) continue; if((a * c - b) * (c + 1) > b * c * (maxd - idx - 1)) break; v[idx] = c; LL na = a * c - b, nb = b * c, t = gcd(na, nb); if(dfs(maxd, idx + 1, na / t, nb / t, c)) ok = 1; } return ok; } int main() { cin >> kase; for(int K = 1; K <= kase; K ++) { forbid.clear(); cin >> a >> b >> k; while(k --) { LL x; cin >> x; forbid.push_back(x); } for(int maxd = 1; ; maxd ++) { memset(ans, -1, sizeof(ans)); if(dfs(maxd, 0, a, b, 0)) break; } cout << "Case " << K << ": " << a << "/" << b; for(int i = 0; ans[i] != -1; i ++) cout << (i ? "+" : "=") << "1/" << ans[i]; cout << endl; } }
四、归纳
- gcd的写法要牢记...不要再写错了,就是一句话 return b == 0 ? a : gcd(b, a % b);
- 一般回溯法可以解决的问题,但是回溯时枚举边界无法确定时,可以使用IDA*算法