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}\),所以
    1. 对于 \(f(x - F_k, k - 1)\),有 \(0 \leqslant x - F_k < F_{k - 1}\),满足断言。进一步,其继续迭代一次仍满足断言;
    2. 对于 \(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;
}
posted @ 2025-02-16 02:02  bluewindde  阅读(9)  评论(0)    收藏  举报