Luogu P4133 [BJOI2012] 最多的方案 题解 [ 蓝 ] [ 线性 DP ] [ 斐波那契数列 ]

最多的方案:模拟赛已严肃被蓝题击杀。

首先有一个暴搜做法,注意到 \(n\le 10^9\) 时斐波那契只有 \(44\) 项,所以可以直接折半搜索,求出方案数。但是 \(n\le 10^{18}\),所以肯定过不了。

有了暴搜之后,观察斐波那契的性质,打表容易发现任意一个数 \(\bm n\) 都能表示为若干个互不相同的斐波那契数之和。证明较为复杂,此处不再赘述。

又因为斐波那契的递推式为 \(a_i = a_{i - 1} + a_{i - 2}\),因此我们在得到了一组构造方案后,可以将方案不断调整,进而得到全部方案

首先考虑如何求出这个可以调整出所有方案的方案。显然,我们可以\(\bm n\) 用尽可能大的斐波那契数分解,因为大的元素总是可以推出小的斐波那契数。

于是,问题被转化为了:有一个序列 \(A\),满足 \(A_i\in\{0, 1\}\)。若 \(A_i = 1\) 则表示 \(F_i\) 出现在了分解的方案中。求满足要求的序列个数。

因为将 \(n\) 用尽可能大的斐波那契数分解保证了最开始的 \(A\) 是字典序最大的序列,而每次拆一个斐波那契数相当于将 \(A_{i - 1}, A_{i - 2}\) 分别加 \(1\)。于是可以设计一个线性 DP\(dp_{i, x, y}\) 表示考虑 \(i\) 的后缀,\(A_{i}\) 进行了 \(x\) 次分解操作,\(A_{i + 1}\) 进行了 \(y\) 次分解操作的方案数。

继续观察,容易发现 \(\bm{x, y}\) 只能取 \(\bm{0/1}\),其余的都是不合法的。证明可以考虑一个数连续分解两次的情况,此时 \(A_{i - 1}\) 也会分解一次,\(A_{i - 2}\) 会分解两次,如此循环下去,一定存在一个数无法变回 \(0\) 或者 \(1\),因此不满足要求。

总体时间复杂度为 \(O(\log n)\)。注意特判 \(A_1 = A_2\) 的情况。

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 105;
ll n, fib[N], a[N], dp[N][2][2];
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    fib[1] = fib[2] = 1;
    for(int i = 3; i <= 90; i++)
        fib[i] = fib[i - 2] + fib[i - 1];
    for(int i = 90; i >= 1; i--)
    {
        if(n >= fib[i])
        {
            a[i] = 1;
            n -= fib[i];
        }
    }
    dp[90][0][0] = dp[89][0][0] = 1;
    for(int i = 88; i >= 2; i--)
    {
        for(int x = 0; x <= 1; x++)
        {
            for(int y = 0; y <= 1; y++)
            {
                int cur = a[i] + x + y;
                if(cur >= 3) continue;
                if(cur == 2) dp[i][1][x] += dp[i + 1][x][y];
                if(cur == 1)
                {
                    dp[i][1][x] += dp[i + 1][x][y];
                    dp[i][0][x] += dp[i + 1][x][y];
                }
                if(cur == 0)
                    dp[i][0][x] += dp[i + 1][x][y];
            }
        }
    }
    cout << dp[2][0][0];
    return 0;
}
posted @ 2025-10-16 21:29  KS_Fszha  阅读(12)  评论(0)    收藏  举报