CF126D Fibonacci Sums 题解

别问为什么现在才来写题解。

题目需要求分解 成若干个不同的斐波那契数列中的数有多少种方案。

由于每个位置最多一次,因此我们可以考虑用一个 字符串来表示。

这个 字符串有两个性质。如果 (换句话说,有连续的 110),那么将其替换成 也是合法的(即,替换为 001)。同理,你也可以考虑拆解。

不妨考虑拆解的情况。你发现,一旦你把 001 拆成 110,那么,第二个 就再也不能继续往下拆了。因为继续拆会变成 11010,第二个 仍然动不了。

证明可以对于字符串的长度归纳证明。换句话说,每个 11 第二个 无论如何都不可能分解成更小的两个 。

我们继续考虑,这样一定会分解成 11010101010 这种情况。碰到了下一个 就没办法继续分裂了(除非下一个 也分裂,但是这样最多多出来一个空位) 。

因此,每一段的分裂是相对独立的。这启示我们从一个分解开始,一直这样分裂下去。我们希望这个分解可以不被任何分裂方法到达,并且所有分解方式都可以被这个分解到达。

换句话说,考虑这样一个问题。考察 字符串,第 位的权值是 ,并且不允许连续两个或以上 在一起(这样挑最后两个 就能合并成更大的 )。我们希望所有 位的权值和是 。

这实际上是一个类似“ 进制分解”之类的东西。我们不难猜出来下面这个结论:

对于任意一种 ,此类分解方式存在且唯一。

“存在”说明我们无论如何都能找到这样一个解。”唯一“则说明任意一个合法分解都可以通过这个分解到达。证明方法是从合法分解中不断挑出来两个相邻的 合并,合并到最后不能合并了就遇到了一个合法的这个分解。由于此类分解唯一,因此我们只要找到了一个解,我们就能断言最后到达的就是这个解。反过来就是说这个解可以到达所有题目要求的分解。

我们下面来证明一下这个命题。由于我们发现了这个命题和 进制分解的相似之处,因此我们也采用类似的证明方法,考虑数学归纳法。

不妨先来证明存在性。首先对于数列中的数 ,存在性是显然的。

不妨来考虑 位 字符串可以表示哪些数。我们考虑 DP,有转移方程 。边界条件是 。我们发现这其实就是斐波那契的第 项(下标从 开始)。

因此,我们可以考虑 位字符串可以表示 之间的数字。考虑归纳法。 的情况可以自行验证。不妨假设前 位都已经成立了。考虑最高位是什么。

  • 对于 这个范围,我们直接最高位置 ,从 的情况继承过来。
  • 对于 。我们最高位置 之后,接上 的一个解。这部分字符串的范围是 ,但我们又加上了 ,那么此时的范围就是 ,也就是 。

下面称这种分解为“标准分解”。

此外,我们刚刚证明了满足条件的不同的字符串个数恰好就是 个。因此这些分解都是唯一的。

唯一性也可以继续数学归纳,不过有点繁琐。但是我做这个题目的时候确实是这样推出来的,不写出来感觉自己亏了。

不妨假设 的部分已经证明完毕了。如果包含 则一定更大,因此长度增加一定不会影响前面的结论。考虑 这一部分。假设这个数是 ,第二种分解存在,那么第二种分解一定不包含 ,因为包含 剩下的数在 之间,根据归纳假设是唯一的,就是我们的标准分解。

那么,考察最高位是 。不妨考虑 这种情况。那么此时根据归纳假设, 的分解包含 ,那它要么不合法,要么就是标准分解。

考察 的情况。不妨考虑 ,那么 。

有归纳假设 的分解是唯一的。那么考虑 的分解是什么样子的。

不如考虑 的分解是什么样子的。手玩一下你会发现一定是 0000...101010101 或者 0000...10010101 这种形式。前面这段可以恰好放下 之间的所有数。

由于 ,那么 肯定小于 ,因此 影响的就是前面一段 0 的位置,对后面的 10 交错的部分不会影响。而在 10 交错的部分,给 置 会导致连锁进位一直进位到 。因此不存在除了标准分解之外的唯一分解。

另外,显然非标准分解额外置一个 也是非标准分解。因此我们就证明完毕了。

唯一分解可以通过贪心求出来,方法就是从高到低能减少就减少。

后面的工作就简单了。当然你可以每段组合数一堆求出来,但是分讨有点困难(考虑下一个分解的上一段会多一个空位)。

因此我们考虑 DP 求解。从高到低确定, 表示前 位,对后来的分解产生的 的影响是 。( 表示 00 没有影响, 表示钦定后两位是 01 即第一位随便第二位一定是 , 钦定后两位是 10,表示第一位一定是 第二位随意,3 表示后面两位都必须是 ,实际 1 不可能出现)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <algorithm>
#include <array>
#include <iostream>
#include <vector>

using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;


namespace solve {
const uint mod = 1e9 + 7;
using mll = ull;

std::vector<ull> fib;

void prework() {
fib.push_back(1);
fib.push_back(2);
while (fib.back() <= 1e18) {
fib.push_back(fib.back() + fib[fib.size() - 2]);
}
return;
}
void solve() {
ull n;
std::cin >> n;
auto rfib = fib;
std::reverse(rfib.begin(), rfib.end());
std::vector<bool> std_fact(rfib.size());

for (size_t i = 0; i < rfib.size(); i++) {
if (n >= rfib[i]) {
n -= rfib[i];
std_fact[i] = true;
}
}

std::vector<std::array<mll, 4>> dp(rfib.size());

dp[0][0] = 1;
if (std_fact[0]) {
dp[0][3] = 1;
}

for (size_t i = 1; i < rfib.size(); i++) {
if (std_fact[i]) {
/**
* 是 1 的情况。
*/

for (size_t j = 0; j < 4; j++) {
if (j == 3) {
continue;
}

if (j != 2) {
dp[i][3] += dp[i - 1][j];
}

if (!(j & 1)) {
dp[i][j >> 1] += dp[i - 1][j];
}
}
}
else {
for (size_t j = 0; j < 4; j++) {
dp[i][j >> 1] += dp[i - 1][j];
if (j == 1) {
dp[i][3] += dp[i - 1][j];
}
}
}
}

std::cout << dp.back()[0] << "\n";
}
} // namespace solve

int main() {
uint t;
std::cin >> t;
solve::prework();
while (t--) {
solve::solve();
}
std::cout << std::flush;
return 0;
}
>

CF126D Fibonacci Sums 题解
https://blogs.sving1024.top/posts/34896/
发布于
2026年7月18日
许可协议