Luogu P4133 [BJOI2012] 最多的方案 题解
记忆化搜索,设 \(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\),则当 \(k\) 足够大时,每两次迭代至少使势能变为原来的 \(2 \phi^2 \approx 0.764\)。
证明
\(k\) 足够大时,斐波那契数列相邻两项比值 \(\frac {F_k} {F_{k + 1}} = \phi\)。
继续上一个证明的分类讨论:
- 第一类情况中,一次迭代使得势能变为原来的 \(\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\)。
因此势能至少变为 \(2 \phi^2\)。
Q.E.D.
所以,总迭代次数约为 \(-2\log_{2 \phi^2}(n) \sim 2\log_{1.309}(n) \sim O(\log_{1.309}(n))\),时间复杂度正确。
注意 \(k\) 较小时,比值大于 \(2 \phi^2\),但由于该估计本身较松,对时间复杂度无影响。
#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;
}