记忆化搜索,设 \(f(x, k)\) 表示当前考虑 \(F_k\),需要分解的数是 \(k\),分类讨论
- 若 \(x = 0\),则 \(f(x, k) = 1\);
- 若 \(k = 1\),则 \(f(x, k) = 0\);
- 若 \(x < F_k\),则 \(f(x, k) = f(x, k - 1)\);
- 若 \(x > F_1 + F_2 + F_3 + \cdots + F_{k - 1} = F_{k + 1} - 1\),则 \(f(x, k) = f(x - F_k, k - 1)\),因为此时不分解出 \(F_k\) 就一定无解;
- 否则,\(f(x, k) = f(x - F_k, k - 1) + f(x, k - 1)\)。
这个做法很轻松地通过了所有数据,我们如何知道它的时间复杂度?
考虑势能分析,设 \(\Phi(n, k)\) 表示计算 \(f(x, k)\) 时 \(x\) 的最大可能值。
断言:要么 \(\Phi(n, k) < F_{k + 1}\),要么 \(\Phi(n, k)\) 在常数次迭代内可以满足前述情况。
证明
边界情况令 \(\Phi(n, k) = n\),接下来假设 \(k > 2\)。
考虑在转移过程中说明断言成立。
- 若 \(x < F_k\),则 \(\Phi(n, k - 1) < F_k\) 满足断言;
- 否则,分类 \(f(x, k) = f(x, k - 1) + f(x - F_k, k - 1)\) 中的两项。注意此时 \(F_k \leqslant x < F_{k + 1}\),所以
- 对于 \(f(x - F_k, k - 1)\),有 \(0 \leqslant x - F_k < F_{k - 1}\),满足断言。进一步,其继续迭代一次仍满足断言;
- 对于 \(f(x, k - 1)\),下一次迭代必然走向 \(f(x - F_{k - 1}, k - 2)\),此时 \(F_{x - 2} \leqslant x - F_{k - 1} < F_{k - 1}\) 满足断言。
Q.E.D.
推论
设 \(\phi = \frac {\sqrt 5 - 1} 2\),则每两次迭代至少使势能变为原来的 \(2 \phi^2 \approx 0.764\)。
证明
继续上一个证明的分类讨论:
- 第一类情况中,一次迭代使得势能变为原来的 \(\phi\),两次即为 \(\phi^2\);
- 第二类情况中,两次迭代使得势能变为原来的 \(2 \phi^2\),两种子情况分别贡献 \(\phi^2\);
- 特殊地,如果第一类情况后接着第二类情况,三次为 \(2 \phi^3\),平均一次 \((2 \phi^3)^{\frac 2 3} = \sqrt[3]4 \phi^2 < 2 \phi^2\)。
Q.E.D.
所以,总迭代次数约为 \(O(-\log_{2 \phi^2}(n)) \sim O(\log_{1.309}(n))\),时间复杂度正确。
#include <iostream>
#include <map>using namespace std;typedef long long i64;const i64 lim = 1e18;int tail;
i64 fib[100];
i64 f[100];map<pair<i64, int>, int> mp;
static inline i64 dfs(i64 x, int k) {if (!x)return 1;if (k == 1)return 0;if (mp.count({x, k}))return mp[{x, k}];if (x < fib[k])return mp[{x, k}] = dfs(x, k - 1);if (f[k - 1] < x)return mp[{x, k}] = dfs(x - fib[k], k - 1);return mp[{x, k}] = dfs(x, k - 1) + dfs(x - fib[k], k - 1);
}signed main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);fib[1] = fib[2] = 1;for (tail = 2; fib[tail] <= lim; ++tail)fib[tail + 1] = fib[tail] + fib[tail - 1];for (int i = 1; i <= tail; ++i)f[i] = f[i - 1] + fib[i];i64 n;cin >> n;cout << dfs(n, tail) << '\n';return 0;
}