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;
}