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日
许可协议