前言:为什么从一个数列开始

你大概见过这串数。

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

第一项是 0,第二项是 1,之后每一项都等于前两项相加。0 加 1 是 1,1 加 1 是 2,1 加 2 是 3,2 加 3 是 5。一个会做加法的小孩能把它续写下去,写一整天都不需要乘除。

但是这串数不太安分。

你拿它写一段最直接的递归程序,它会反过来教你:看上去最像数学定义的程序,跑起来最不像样。

你拿它数一段走廊能用多少种方式铺砖,它会从你手里那一块块砖里长出来。

你把它装进一个 2×22 \times 2 的小方块,它就变成"状态怎么从这一步变到下一步"。

你问它第 nn 项到底是多少,它会从一串整数里拿出一个 5\sqrt{5} 给你看。

你只盯着它的最后一位数字,它会像钟表一样循环,60 步回到原地。

你抛硬币,要求不出现连续两个正面,它又一次跑出来,当起了计数员。

这么多门,都被同一串数推开。

听起来像在讲不同的故事。一段慢程序、一条铺砖走廊、一个 2×22 \times 2 矩阵、一个 5\sqrt{5}、一个 60 步的循环、一个抛硬币的游戏。它们都从同一条小规则里长出来:每一项等于前两项之和。

这本书想讲的就是这件事。

黄金比例会出现,但不在这本书的中心

黄金比例 φ=1+52\varphi=\frac{1+\sqrt5}{2} 这本书里会出现。它必须出现,因为它和斐波那契数列真正连在一起:相邻两项之比会越来越靠近 φ\varphi,闭式公式里也直接写着它。这部分很美,我们会慢慢看清楚它是怎么进来的。

φ\varphi 之外,松果、向日葵、鹦鹉螺、希腊神庙、达·芬奇素描、各种宽长比的人脸,这些就不在这本书里了。它们都好看,也都真实存在,但停在这一步有点亏。

数学最好看的部分,常常在"它为什么会这样"。一个现象只被展示成奇迹,我们能欣赏一秒。一个现象被拆开看清楚机制,我们能带走它一辈子。

所以黄金比例会出现,作为整条故事线里的一个角色,在它该出现的位置出现。

这本书真正要看什么

同一个递推 Fn=Fn1+Fn2F_n=F_{n-1}+F_{n-2} 可以被写成定义、被执行成程序、被存成一张表、被加速成对数时间的算法、被压缩进一个 2×22 \times 2 矩阵、被解成带有 5\sqrt{5} 的闭式公式、被一段一段铺成砖、被画成点和线、被追问整除性、被模掉一个数看余数、被嵌进一个概率分子里。

这些是同一种结构换了十一种说法。

每一种说法背后,是一种数学分支在问自己的问题。

程序会问:怎么算?算法会问:什么工作在重复,怎么省掉它?矩阵会问:状态怎么从这一步变到下一步?闭式公式会问:能不能直接跳到第 n 项?组合数学会问:这个数到底在数什么?图论会问:能不能把它画成点和线?数论会问:整数运算里留下了什么规律?模运算会问:只看余数会怎样?概率会问:所有可能里,有多少个是我们想要的?

斐波那契数列的好处是:换一个问题,不用换对象。

它够小,能放在脑子里。它够厚,能撑住一整本这种来回翻译的练习。

学完之后,你不会变成斐波那契专家。但你会比以前更习惯一件事:数学里那些被切成不同课程的内容,其实从来没有真正分开过。它们只是同一种结构被不同的人用不同的口音说出来。

一个小例子,先垫垫底

讲一个具体一点的,好让你知道这本书喜欢什么样的瞬间。

想象一条长度为 4 的窄走廊,你要用 1 格砖和 2 格砖把它铺满。问有多少种铺法。

我们干脆把所有铺法列出来:

1+1+1+1
1+1+2
1+2+1
2+1+1
2+2

一共 5 种。

这个 5 是五种你能亲手摆出来的铺法。你能拿五张纸各画一种,铺在地上对比。

现在再看一条长度为 nn 的走廊。最后一块砖要么是 11 格,要么是 22 格。如果最后一块是 11 格,前面剩下长度 n1n-1 的走廊,铺法数就是长度 n1n-1 的铺法数。如果最后一块是 22 格,前面剩下长度 n2n-2 的走廊,铺法数就是长度 n2n-2 的铺法数。

所以总数满足 A(n)=A(n1)+A(n2)A(n) = A(n-1) + A(n-2)。它就是斐波那契递推。

这一次,斐波那契递推从"最后一块砖到底是 1 格还是 2 格"这个问题里长出来。

这本书喜欢的就是这种时刻:一个具体的小例子,一个简单的分情况,一个熟悉的结构突然冒出来。

比魔术更稳,也比魔术更耐看。

这本书的结构

全书分前言、二十一章正文、尾声。

第一部分"递推"是第 1 到 6 章。第 1 章从兔子、楼梯、铺砖三个场景长出递推。第 2 章把这段递推翻译成递归程序,跑几个例子,第一次感觉慢。第 3 章展开调用树,分析慢的原因。第 4 章给程序装上记忆,把指数级压成线性。第 5 章让程序跳步,用快速倍增做到对数时间。第 6 章再用矩阵快速幂,给出另一种对数时间算法。

第二部分"换语言"是第 7 到 15 章,九章六种语言。第 7 章用矩阵看(状态视角),第 8 章用闭式公式看(推导 Binet 公式),第 9 章追问 5\sqrt{5} 怎么消失,第 10 章用铺砖和楼梯的计数看,第 11 章把计数推广到子集和二进制串,第 12 章用图看,第 13 章用恒等式的代数证法看,第 14 章用恒等式的组合证法看,第 15 章用生成函数看。

第三部分"整数性质"是第 16 到 19 章。第 16 章看整除性,第 17 章看模周期(发现循环),第 18 章解释为什么是 60(素数情况与中国剩余定理),第 19 章看 Zeckendorf 表示,用斐波那契数反过来表示任意整数。

第四部分"推广"是第 20 和 21 章。第 20 章把斐波那契搬到抛硬币概率里,第 21 章问"等到首次出现连续两个正面平均要抛几次",并推广到 k-bonacci。尾声把视角拉到一般线性递推,回到"换语言"这条主线。

每章大约三四千字。可以按顺序读,也可以跳着读。但建议前六章按顺序,因为后面反复用到第 2、3、4 章里建立的几个工具。

一句关于精度的说明

不同书对斐波那契数列的起点写得不一样。有的从 F1=1,F2=1F_1 = 1, F_2 = 1 开始,有的从 F0=0,F1=1F_0 = 0, F_1 = 1 开始。

本书统一用 F0=0,F1=1F_0 = 0, F_1 = 1。这样写的好处是和矩阵、闭式公式、组合计数都对得整齐。代价是某些常见说法要平移一下:比如"爬 nn 阶楼梯的方法数等于 Fn+1F_{n+1}"而不是 FnF_n。索引看起来差 11,但里面的逻辑是稳的。

这种细节在书里第一次出现时会说清楚。之后用到就不再重复。

小数字是护栏。每次写完一个公式,我会拿 n=1,2,3n = 1, 2, 3 代进去试一下。如果对不上,我们就回去修索引,而不是挥手遮过去。

这是数学的一部分。

最后说一下语气

我尽量不用"神奇""奥秘""宇宙密码"这一类词。它们把数学讲成巫术,听起来热闹,听完什么也带不走。

我尽量不用"本章我们将""由此可见""具有重要意义""我们可以得出三点启示"这种教材腔。它们把数学讲成文件,读起来像在签合同。

我想写的语气大概是这样:

"你看,这儿有个小机关。我们把它拆开看看。拆完你会发现,它还连着后面那一串东西。"

仅此而已。

开始

我们从最普通的一个动作开始:问下一项从哪儿来。

一个当前的数,可能由前面几个更小的数决定。一个大问题,可能可以拆成两个更小的问题。最后一步,可能就解释了整个递推。

这是第一扇门。

推开门,后面那些门会一扇一扇亮起来。

第 1 章 一个递推是怎么长出来的

1202 年,比萨的莱昂纳多写了一本叫《计算之书》的著作。莱昂纳多有个外号叫 Fibonacci,意思是波那契的儿子。这本书把印度-阿拉伯数字和算法介绍到欧洲,影响很大。书里第 12 章有一个问题,大意是这样:

有人把一对刚出生的小兔子放进一个围起来的地方。小兔子要一个月长大,长大之后每个月生一对新的小兔子。假设兔子都不死,问一年后有多少对兔子。

我们一格一格地数。

第 0 个月:1 对小兔子。
第 1 个月:还是 1 对,但它们已经长大。
第 2 个月:这对大兔子生了 1 对小兔子,所以总共 2 对。
第 3 个月:原来的大兔子再生 1 对小兔子,上个月的小兔子刚长大还不生,所以总共 3 对。
第 4 个月:两对大兔子各生一对,上个月新生的还太小不生,所以总共 5 对。

继续下去:8 对、13 对、21 对、34 对。

这串数就是 1, 1, 2, 3, 5, 8, 13, 21, 34。

兔子故事本身不重要。重要的是它怎么算出来的。

把"本月怎么从上月变来"分成两种情况

每个月的兔子总数,可以分成两群:上个月就已经在的兔子,加上这个月新生的兔子。上个月就已经在的兔子,这个月都还活着(题目假设兔子不死),所以这一群就是上个月的总数。

新生兔子有多少对?等于上个月已经长大、可以生小兔子的兔子对数。哪些兔子在上个月已经长大?再上一个月就已经在的所有兔子。因为它们要么当时已经长大,要么上个月从小变大会在下个月开始生。所以"新生兔子对数"等于"再上一个月的兔子总数"。

把这个分情况用一句话写出来:

本月总数 = 上月总数 + 再上月总数。

如果记第 nn 个月的兔子对数为 RnR_n,那么 Rn=Rn1+Rn2R_n=R_{n-1}+R_{n-2}。兔子数列从 1,11,1 开始,整个故事就被一行式子压缩完了。

这是斐波那契递推的第一次出现。它从"今天和昨天有什么关系"分成两种情况,然后写下来。

把这个动作看清楚

刚才那段推导里藏着一个动作。这个动作后面会反复用到,所以这里值得把它讲清楚。

我们想要的是"第 n 个月有多少对兔子"。我们没有直接去算它。我们把它拆成了两种情况:上个月就已经有的兔子,加上这个月新生的兔子。这两种情况加起来就是全部兔子,而且不重不漏。

拆完之后,"上个月就已经有的兔子"等于"上月总数",这没什么好说的。"这个月新生的兔子"看起来要再分析一遍,但好在它只依赖"再上一个月的兔子数",比 n 小,所以可以把这个子问题的答案直接拿来用。

把一个大问题拆成几个小问题,每个小问题的规模都比原问题小,然后把它们的答案合起来。这是递推的核心动作。后面第 2、3、4 章会反复看到这个动作,只不过小问题不再是"上月和再上月",而是更细致的拆法。

如果递推式是这种"分最后一步"的产物,那它就是一个对"系统的下一步怎么从这一步长出来"的描述。这一点在后面看动态规划、看矩阵、看组合计数的时候会越来越清楚。

一个更干净的例子:楼梯

兔子问题有人物、有时间、有月份,记起来稍微要绕一下。我们换一个更干净的版本。

你站在一段 nn 阶楼梯下面。每次你可以走 1 阶或者 2 阶。问走到顶有多少种走法。

我们让 WnW_n 表示走 nn 阶楼梯的方法数。

n=1n = 1:只能一步走完,方法数 1。
n=2n = 2:可以一步一步走,也可以一步直接跨 2 阶,方法数 2。
n=3n = 3:可以 1+1+1、1+2、2+1,方法数 3。
n=4n = 4:1+1+1+1、1+1+2、1+2+1、2+1+1、2+2,方法数 5。

得到 1, 2, 3, 5。和兔子数列错位了一下,但形状一样。

为什么会这样?

站在 nn 阶楼梯下面,你的第一步要么走 1 阶,要么走 2 阶。

如果第一步走 1 阶,那你之后还剩 n1n-1 阶要走,剩下的走法数等于 Wn1W_{n-1}

如果第一步走 2 阶,那你之后还剩 n2n-2 阶要走,剩下的走法数等于 Wn2W_{n-2}

所以 Wn=Wn1+Wn2W_n=W_{n-1}+W_{n-2}。这就是斐波那契递推。和兔子问题一模一样。

楼梯的好处是没有兔子,没有月份,没有"上个月再生一对"这种需要画图的事。整个递推就在"第一步走几阶"这一刀里完成。

到这里,斐波那契递推已经在两个看起来不相干的故事里出现了。这件事本身是个暗示:它要说的不是兔子,也不是楼梯,而是"系统的下一步由前两步决定"这种结构本身。

把索引说清楚

斐波那契数列在不同书里的写法不一样。有的从 F1=1,F2=1F_1=1,F_2=1 开始,第 0 项不存在。有的从 F0=0,F1=1F_0=0,F_1=1 开始,把第 0 项写出来。

这本书统一用后者:F0=0,F1=1F_{0} = 0, F_{1} = 1。也就是说,完整地写出来是 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …。

这样写的好处,到第 7 章矩阵和第 8 章闭式公式那里就会看出来,所有的公式都对得整整齐齐。代价是要记一下:兔子问题和楼梯问题给出的数列,刚好错位一格。

具体来说:

  • 兔子问题里的"第 n 个月兔子对数"对应 Fn+1F_{n+1}。第 00 个月是 11 对,等于 F1=1F_1=1;第 11 个月是 11 对,等于 F2=1F_2=1;第 22 个月是 22 对,等于 F3=2F_3=2,以此类推。
  • nn 阶楼梯的方法数 WnW_n 对应 Fn+1F_{n+1}W1=1=F2W_1=1=F_2W2=2=F3W_2=2=F_3W3=3=F4W_3=3=F_4,……

为什么这种错位一直出现?因为"事件从 1 开始数"和"数列从 0 开始数"在描述同一件事时,自然差一格。我们要做的把它说清楚,让小数字充当护栏。

后面所有的组合计数问题,铺砖、不连续子集、图上匹配,我们都会按同样的方式做:先把小数字列出来,再问"对应到 F 的哪一项"。错了就修,不挥手。

再换一个外壳:铺砖

离开兔子,离开楼梯,再看一个完全不同场景里同一个递推。

考虑一条长度为 nn 的 1 维走廊。你手上有 1 格砖和 2 格砖。问把走廊铺满有多少种铺法。记这个数为 TnT_n

n=1n = 1:只能用 1 块 1 格砖。T1=1T_1=1
n=2n = 2:可以两块 1 格砖,也可以一块 2 格砖。T2=2T_2=2
n=3n = 3:1+1+1、1+2、2+1。T3=3T_3=3
n=4n = 4:1+1+1+1、1+1+2、1+2+1、2+1+1、2+2。T4=5T_4=5

1, 2, 3, 5。又是它。

最后一块砖要么是 1 格,要么是 2 格。如果最后一块是 1 格,前面剩下长度 n1n-1 的走廊,铺法数等于 Tn1T_{n-1}。如果最后一块是 2 格,前面剩下长度 n2n-2 的走廊,铺法数等于 Tn2T_{n-2}。所以 Tn=Tn1+Tn2T_n=T_{n-1}+T_{n-2}

铺砖、楼梯、兔子,三个完全不同的故事,推出来的递推式完全一样。它们都是"系统的下一步由前两步决定"这个结构的不同外衣。

你换衣服,结构不变。

第 10 章会把铺砖问题正式当成组合计数的入口,到时候我们还会再回来仔细数一数,看这个 5 是怎么从五块真实的砖头里数出来的。这里只是先让你看一眼:斐波那契数能被一块一块摆出来。

把它写成一个定义

现在我们有了递推 Fn=Fn1+Fn2F_n=F_{n-1}+F_{n-2} 和初值 F0=0,F1=1F_0=0,F_1=1。这就构成了一个完整的定义。给定任意一个非负整数 n,按这个定义都能算出一个唯一的 FnF_n

定义只说了"FnF_n 等于前两项之和"。它没说"怎么算 FnF_n"。

这两件事看起来一样,其实差很远。定义是数学的事,它告诉你 FnF_n 是什么。算法是计算的事,它告诉你怎么把这个值拿出来。

把定义直接翻译成算法,最朴素的版本就是:

function F(n):
    if n == 0: return 0
    if n == 1: return 1
    return F(n-1) + F(n-2)

这段程序读起来和数学定义几乎一模一样。它会正确给出 FnF_n。但下一章我们会看到,它也是你写过最慢的程序之一。

斐波那契给我们的第一课来自这里:数学定义和高效算法之间有一道沟。优雅的定义不保证优雅的计算。

这个教训听起来好像只关于斐波那契。其实它出现在很多地方。一个图的最短路可以用递归定义写得很短,但直接跑会爆炸。一个组合数 (nk)=(n1k1)+(n1k)\binom{n}{k}=\binom{n-1}{k-1}+\binom{n-1}{k} 看上去人畜无害,直接展开会算到天荒地老。一个概率分布的边缘分布可以用全概率公式写一行,真去算要遍历整个样本空间。

这些场景看起来各不相同,背后都是同一件事:定义回答"是什么",算法回答"怎么算",二者之间常常差一个聪明的中间步骤。

斐波那契的好处是它小到可以看清这个差距是怎么出现的,又大到可以让我们看清楚怎么把差距填上。

这章我们看到了什么

兔子问题、楼梯问题、铺砖问题,三个看起来毫无关系的故事,最后都给了我们同一个递推 Fn=Fn1+Fn2F_n=F_{n-1}+F_{n-2}。这件事本身就在告诉我们:递推从"把系统的下一步分成几种情况"这一动作里自然长出来。

我们把索引说清楚了:本书统一用 F0=0,F1=1F_0=0,F_1=1,组合计数问题对应的下标会差一格,错位时用小数字修。

最后我们把递推直接翻译成了一段最朴素的递归程序,并且发现:定义看起来够清楚了,可是它并没有告诉我们"怎么算才不慢"。

第一扇门开了。后面那扇门后面是一段慢得离谱的程序,慢到你会怀疑电脑是不是坏了。

我们去把它拆开看看。

第 2 章 把它翻译成程序:朴素递归

上一章末尾那段递归程序,我们这一章真的拿去跑一下。

function F(n):
    if n == 0: return 0
    if n == 1: return 1
    return F(n-1) + F(n-2)

读起来和数学定义 Fn=Fn1+Fn2F_n=F_{n-1}+F_{n-2} 几乎一一对应。你看不出它有什么问题。它也没有 bug。它给出的答案永远是对的。

但是你跑 F10F_{10},瞬间。F20F_{20},瞬间。F30F_{30},等 0.1 秒。F40F_{40},等几秒。F45F_{45},等一分钟。F50F_{50},你可以去泡杯茶。F100F_{100},你这台机器大概这辈子算不完。

明明每一步都只是加法,慢在哪里。

先跑几个小例子

F5F_5 看看。表面上是 5 次递归调用。结果是 5。一次加法出来。看上去没什么。

F10=55F_{10}=55。从 10 个递归调用一直到几十次调用,结果是 55,瞬间给出。

F20=6765F_{20}=6765。还是瞬间,但仔细看,调用次数已经不是 20 次了,是 21891 次。

F30=832040F_{30}=832040。0.1 秒左右。调用次数约 270 万。

F40=102334155F_{40} = 102334155。几秒。总调用数 T40=2F411=331160281T_{40}=2F_{41}-1=331160281,约 3.3 亿次。

F50=12586269025F_{50} = 12586269025。几分钟到十几分钟。总调用数 T50=2F511=40730022147T_{50}=2F_{51}-1=40730022147,约 407 亿次。

数字看上去已经不对劲了。F40F_{40} 这台机器几秒就给出 1 亿这个量级的数,但调用次数居然达到 3 亿。也就是说,每次调用平均只产生不到一位数的有效计算。这种"调用次数远超结果位数"的现象,是出问题的征兆。

我们这一章不展开分析。先记下这个现象:朴素递归的调用次数随 n 增长极快,比 FnF_n 本身增长还快一档。

下一章我们把调用过程画出来,看清楚这些多出来的调用在做什么。

看一眼小例子的展开

虽然这一章不展开调用树,但跑 F5F_5 还是要看一眼的,否则现象就太抽象了。

F5F_5 要算 F4F_4 + F3F_3
F4F_4 要算 F3F_3 + F2F_2
F3F_3 要算 F2F_2 + F1F_1
F2F_2 要算 F1F_1 + F0F_0

最底层 F0F_0F1F_1 直接返回 0 和 1,递归开始往回走。但是注意上面那一行:F3F_3F5F_5 的展开里出现了两次,一次是 F4F_4 的右边,一次是 F5F_5 自己的右边。F2F_2 出现了三次。

F5F_5 总共产生 15 次调用。但是真正"新"的计算只有 F0F_0, F1F_1, F2F_2, F3F_3, F4F_4, F5F_5 这 6 项。剩下 9 次调用,都是把已经算过的东西再算一遍。

这才 n = 5。换成 n = 10,整个调用过程里 F2F_2 被调用 34 次,F1F_1 被调用 55 次,F0F_0 被调用 34 次。每一项都被反复重算。

到 n = 30,F2F_2 被算了大约 832040 次。一项被算 80 多万次,这就是为什么 0.1 秒才出结果。

到 n = 50,F2F_2 被调用 F49=7778742049F_{49} = 7778742049 次,约 78 亿次。

到这一步可以下一个粗略判断:朴素递归慢,是因为它在反复算同一件事。

慢的根源是结构性的

慢的根源不在加法本身。加法在所有现代 CPU 上都是纳秒级的。即使 F50F_{50} 真的要做 407 亿次加法,按每秒 10 亿次算,也就 40 秒。可朴素递归跑 F50F_{50} 要十几分钟,差了一个数量级。所以慢的根源不只是"加法次数多"。

慢的真正根源是:朴素递归算了就忘。每次遇到 FkF_{k},它都从头开始把 FkF_{k} 重新算一遍,完全不管刚才是不是已经算过。

这种"算了就忘"是结构性的,不是某个具体实现的偶然缺陷。递归程序按定义展开,定义本身没说"已经算过的就别再算",所以程序也不会这么干。这是数学定义和高效算法之间那道沟的具体形状:定义回答"是什么",算法听成"每次需要就从头算一遍"。

斐波那契给我们的第二课来自这里:优雅的定义翻译成朴素递归,会变成计算灾难。

这不是递归的错。递归只是一种写法,本身不慢。慢的是这种特定递归,它不保存子问题答案。换成另一种递归(带记忆的递归),同一道题可以快几个数量级。

"翻译"这件事

我们再多想一步。把数学定义翻译成程序,听起来像是一个机械的过程:定义里有什么,程序就写什么。递推式 Fn=Fn1+Fn2F_n=F_{n-1}+F_{n-2} 被直接翻译成 fib(n)=fib(n-1)+fib(n-2),看起来一一对应。

但是这种"一一对应"忽略了几个东西。

第一,定义没说"什么时候算"。数学里 FnF_n 是一个抽象的值,它"就是"那个数。程序里 FnF_n 是一个动作,它在某个时刻被执行,产生一个结果。这两个 FnF_n 看起来一样,性质完全不同。

第二,定义没说"算过的要不要记"。数学里 F3F_3 永远等于 2,无论你查多少次。程序里 F3F_3 是一个调用,每次调用都重新算,结果当然是 2,但算的过程花时间。

第三,定义没说"按什么顺序算"。数学里 F5F_5 = F4F_4 + F3F_3 是一个等式,左右两边同时成立。程序里要先算 F4F_4 再算 F3F_3,或者反过来,顺序由实现决定。不同的顺序可能影响效率。

这三个"没说"加起来,意味着从定义到程序之间有相当大的自由度。朴素递归只是其中一种翻译方式,它选择了"按定义顺序算、算完就丢、不记忆"。这种选择看起来最自然,恰恰因为它最自然,所以也最容易踩坑。

其他翻译方式包括:自顶向下带记忆的递归(先按定义展开,但算过的存下来),自底向上迭代(从 F0F_0F1F_1 开始往大算,不用递归),分治加速(发现递推里藏着跳步结构,对数时间搞定)。这些方式都对应同一个数学定义,但性能差好几个数量级。

第 3 章我们会展开调用树,把"算了就忘"这件事画清楚。第 4 章给程序装上记忆。第 5、6 章让程序跳步。每一步都是在重新填"定义"和"算法"之间的那道沟。

一个直觉:朴素递归比 FnF_n 本身慢一档

虽然这一章不展开复杂度推导,但有一个直觉值得现在就建立。

朴素递归的调用次数 TnT_n 满足 Tn2Fn+1T_n\approx2F_{n+1}。也就是说,调用次数和斐波那契数本身同阶,但稍微大一点,差一个 2 倍和一个位移。

这个事实有几个直接的后果:

  • FnF_n 增长得很快(指数级,约 φn/5\varphi^n/\sqrt5)。
  • TnT_n 也增长得很快(也是指数级,约 2φn+1/52\varphi^{n+1}/\sqrt5)。
  • nn 增加 1,调用次数大约乘以 1.618(黄金比例)。
  • nn 增加 10,调用次数大约乘以 122 倍。
  • nn 增加 50,调用次数大约乘以 101010^{10} 倍。

这就解释了为什么 F40F_{40} 几秒,F50F_{50} 几分钟,F60F_{60} 大概要好几小时。每多 10 项,时间乘以 122。

下一章我们会把这个直觉推导清楚:为什么 Tn2Fn+1T_n\approx2F_{n+1}?为什么朴素递归的复杂度恰好是斐波那契数本身?这个"递归算斐波那契的代价是另一个斐波那契数"的事实,是这本书最讽刺的瞬间之一。

这章我们看到了什么

朴素递归跑小例子没事,跑大例子就慢得离谱。慢的根源不在加法本身,而在"算了就忘":每次遇到 FkF_{k} 都从头算,不管之前算没算过。

我们把"翻译"这件事看了一眼:从数学定义到程序之间有好几个"没说",朴素递归只是其中一种最自然的翻译,恰恰因为它最自然,所以最容易踩坑。

最后留下一个直觉:朴素递归的调用次数和斐波那契数本身同阶,Tn2Fn+1T_n\approx2F_{n+1}。这意味着 nn 每增加 1,时间乘以 1.618。下一章我们会把这个直觉推导清楚,看清楚那棵调用树到底长什么样。

第 3 章 调用树与"为什么这么慢"

第 2 章那段程序,我们说要回过头看它怎么慢的。这一章把它的调用过程画出来,让慢这件事有具体的形状。

F5F_5 的调用展开

F5F_5 的时候,调用过程是这样的:

F5F_5F4F_4 + F3F_3
F4F_4F3F_3 + F2F_2
F3F_3F2F_2 + F1F_1
F2F_2F1F_1 + F0F_0
F1F_1F0F_0 直接返回。

把它画成一棵树:

                       F(5)
                     /      \
                  F(4)        F(3)
                 /    \      /    \
              F(3)   F(2)  F(2)  F(1)
             /    \   / \   / \
          F(2) F(1) F(1)F(0)F(1)F(0)
          /  \
       F(1) F(0)

数一下每个节点被算了多少次。

F1F_1 出现 5 次。
F0F_0 出现 3 次。
F2F_2 出现 3 次。
F3F_3 出现 2 次。

整个树总共 15 个节点。但是真正"新"的计算只有 F0F_0, F1F_1, F2F_2, F3F_3, F4F_4, F5F_5 这 6 项。剩下 9 次调用,都是把已经算过的东西再算一遍。

这才 n = 5。换成 n = 10 试试。F9F_9 是一棵独立的斐波那契调用树,F8F_8 也是一棵独立的斐波那契调用树。它们之间共享的部分,F8F_8, F7F_7, F6F_6, …,全部被算了两次。

到 n = 30,整棵树大概有 270 万个节点。到 n = 40,约 3.3 亿个。到 n = 50,约 407 亿个。这就是为什么 F50F_{50} 跑不动。要做的加法次数太多。

把"太多"算出来

TnT_n 是计算 FnF_n 所需的递归调用次数。从程序结构看:

T0=1,T1=1,Tn=Tn1+Tn2+1(n2). T_0=1,\qquad T_1=1,\qquad T_n=T_{n-1}+T_{n-2}+1\quad(n\ge 2).

这里的 +1+1 是因为 FnF_n 自己也算一次调用。

Tn+1T_n+1 记作 UnU_n,那么

Un=Tn+1=Tn1+Tn2+2=Un1+Un2. U_n=T_n+1=T_{n-1}+T_{n-2}+2=U_{n-1}+U_{n-2}.

又有 U0=2,U1=2U_0=2,U_1=2。所以 UnU_n 满足的就是斐波那契递推,只是初值是 2,22,2 而不是 0,10,1。这意味着

Un=2Fn+1. U_n=2F_{n+1}.

也就是说

Tn=2Fn+11. T_n=2F_{n+1}-1.

朴素递归计算 FnF_n 的调用次数,差不多是 2 Fn+1F_{n+1}

这是一个很讽刺的结果。你写程序去算 FnF_n,结果程序做的工作量正比于 Fn+1F_{n+1}。你想算的数,恰好就是你要付出的代价。斐波那契数列增长得很快,所以你的程序也慢得很快。

具体一点:

  • F10=55F_{10} = 55T10=2F111=2891=177T_{10}=2F_{11}-1=2\cdot89-1=177 次调用。瞬间。
  • F20=6765F_{20} = 6765T20=2F211=2109461=21891T_{20}=2F_{21}-1=2\cdot10946-1=21891 次调用。瞬间。
  • F30=832040F_{30} = 832040T30=2F311=213462691=2692537T_{30}=2F_{31}-1=2\cdot1346269-1=2692537,约 270 万次。0.1 秒左右。
  • F40=102334155F_{40} = 102334155T40=2F411=331160281T_{40}=2F_{41}-1=331160281,约 3.3 亿次。几秒。
  • F50=12586269025F_{50} = 12586269025T50=2F511=40730022147T_{50}=2F_{51}-1=40730022147,约 407 亿次。一个半小时起步。
  • F1003.5×1020F_{100} \approx 3.5 \times 10^{20}T1001021T_{100}\approx10^{21} 次调用。即使每次调用 1 纳秒,也要 3 万年。

同一个子问题被算了无数次。F2F_2 在算 F50F_{50} 时被算了大约 F49F_{49} ≈ 78 亿次。F3F_3 被算了大约 F48F_{48} ≈ 48 亿次。一棵看似简单的二叉树,因为子问题之间完全重叠,节点数指数级膨胀。

调用树的两条对角线

如果你想再看得精确一点,调用树上有两条线特别有讲究。

第一条线:最左路径。从根 FnF_n 一路向左走 Fn1F_{n-1}, Fn2F_{n-2}, …, F1F_1, F0F_0。这条线有 n + 1 个节点,对应"从顶向下递归到最底层"的过程。任何递归实现都必须走完这条线,因为你至少要碰到 F0F_0F1F_1 才能开始返回。

第二条线:最右路径。从根 FnF_n 一路向右走 Fn2F_{n-2}, Fn4F_{n-4}, Fn6F_{n-6}, …。这条线长度大约 n/2,也是任何递归实现都要走的。

整个调用树的节点数,主要由"在最左路径基础上往右伸展出去的子树"决定。根的右子树是 Fn2F_{n-2},它本身又是一棵完整的斐波那契调用树。这棵子树的根的右子树是 Fn4F_{n-4},又是一棵完整的斐波那契调用树。以此类推。

把这些右子树加起来,你会发现总节点数恰好满足斐波那契递推。所以 TnT_n ≈ 2 Fn+1F_{n+1} 这件事,从结构上看就是:调用树是一棵"每一层都长出和上一层差不多大子树"的二叉树,子树之间的内容大量重叠,但程序并不识别这种重叠。

调用树的形状本身就是斐波那契

到这里可以下一个稍微深一点的判断。

朴素递归的调用树,结构上和斐波那契数列同构。每个节点 FkF_{k} 的子树大小,等于 Tk2Fk+1T_k\approx2F_{k+1}。每个节点的左子树大小约等于 2 FkF_{k},右子树大小约等于 2 Fk1F_{k-1}。两棵子树加起来差不多等于父节点的子树大小。

这件事的几何效果是:朴素递归的调用树长得像一棵"斐波那契分形"。每个子树都是父树的缩小版,缩小比例大约是 1/φ\varphi 和 1/φ2\varphi^{2}。整棵树的节点数指数级膨胀,膨胀速度就是斐波那契数列本身的增长速度。

这是这本书里最讽刺的一个瞬间。你写程序去算斐波那契数,结果程序构造的调用树本身就是斐波那契形状的。你想算的那个数,恰恰就是你程序的代价。

问题不在递归,问题在没记性

到这里你可能会想:递归是不是不能用?

递归只是一种写法,本身不慢。慢的是这段程序"算了就忘"。

你看,F5F_5 在算 F4F_4 的时候已经把 F3F_3 算了一遍。等它回到顶层去算 F3F_3 的时候,它根本不知道自己刚才算过这玩意儿,从头再来一次。

把同一份工作做了两遍,又两遍,又两遍。整个调用树的膨胀不因为这个程序写得多复杂,而是因为最简单的子问题,F2F_2, F3F_3, F4F_4,被反复重算。

打个比方。你去超市买东西,列了一张单子:牛奶、面包、鸡蛋、苹果、香蕉。买完回家发现忘了牛奶。再去一次。又买完回家发现还忘了面包。再去一次。如果每次只解决一个遗忘项,你跑超市的次数等于商品数。

朴素递归就是这样。它每遇到一次 F2F_2 就重新算一遍,每次都从最底下 F0F_0F1F_1 一路加起来。它没有一张"已经算过"的备忘录,所以每次都从头来。

让程序长出一张备忘录,是下一章要做的事。这一章我们先把"为什么这么慢"这件事彻底看清楚。

一个更直观的失败

把斐波那契递归展开成调用树,能看出它哪里慢。但这件事其实可以用一个更直观的方式感受。

你跑 F50F_{50} 在你的笔记本上要等几分钟。你的笔记本每秒能做几十亿次基本运算。也就是说,在这几分钟里,你的电脑做了 101110^{11} 次以上的加法。而 F50F_{50} 这个数本身,按定义只需要 50 次加法就能算出来。

50 步加法,几纳秒就完事。可是朴素递归花了几分钟,做了 101110^{11} 次运算。中间差的 101010^{10} 倍,全是浪费。

这就是定义和算法之间那道沟的具体形状。定义说"FnF_n 等于 Fn1F_{n-1} + Fn2F_{n-2}",算法听成"每次需要 FnF_n,就把 Fn1F_{n-1}Fn2F_{n-2} 都从头算一遍"。这中间没有任何"已经算过就别再算"的意识。

朴素递归把数学定义的优雅翻译成了计算灾难。这是递归的特定写法的错,不是递归本身的错。

这本书第一次出现的反常识

到这里我们可以把这一章的核心说出来。

我们通常觉得,一段程序越简洁、越接近数学定义,它就越优雅,越值得写。这一章里,斐波那契递归就是这种优雅的化身:6 行代码,结构清楚,读起来像诗。

但是优雅的定义不保证优雅的计算。这段最像数学定义的程序,跑起来却极慢。

这件事在算法课里被叫做"定义 \neq 算法"。听起来朴素,但是真碰到的时候很容易栽。斐波那契递归是栽这个跟头最好的地方,因为它小到能让你看清跟头是怎么栽的,又大到能让你感觉到疼。

后面第 4 章会讲怎么修,给程序加上一张备忘录,让它从指数级变线性。第 5 章会讲怎么修得更狠,把步子折半,做到对数级。第 6 章会把整个递推改写成矩阵快速幂。每一步都是在重新填"定义"和"算法"之间的那道沟。

但是这一章我们停下来看一下:原来这道沟,是真的存在的。即使是这么简单的一段递归,也会有。

一个值得记住的画面

最后留一个画面。

那棵调用树。每个节点上写着一个 FkF_{k}。整棵树有 2 Fn+1F_{n+1} − 1 个节点,但你真正需要的"新计算"只有 n + 1 个:F0F_0, F1F_1, F2F_2, …, FnF_n

剩下所有的节点,都是把已经算过的东西重算一遍。

那些重算的节点,是浪费。它们也是机会。只要把每个 FkF_{k} 算过一次之后存下来,下次再遇到就直接查表,所有的重算节点会瞬间消失。一棵 2 Fn+1F_{n+1} 节点的树,会塌缩成 n + 1 个节点的链。

下一章我们就让这棵树塌缩。

第 4 章 给程序装上记忆:动态规划的内核

第 3 章那棵膨胀的调用树,我们说它"算了就忘"。这一章给程序装上一张备忘录,让算过的东西别再算第二遍。整个改动很小,但效果是数量级的。

第一步:把算过的存下来

最直接的修改,是给 FkF_{k} 配一张表。每次算 FkF_{k} 之前先查表,如果表里已经有,直接拿来用。如果还没有,算一遍,把结果写进表里,再返回。

代码大概长这样:

memo = {0: 0, 1: 1}

function F(n):
    if n in memo:
        return memo[n]
    result = F(n-1) + F(n-2)
    memo[n] = result
    return result

加了两行代码,效果完全不一样。

F50F_{50}:原来要算约 407 亿次,现在算 51 次(F0F_0F50F_{50} 各一次)。跑 F100F_{100}:原来要算 102110^{21} 次,现在 101 次。

为什么差距这么大?因为每个 FkF_{k} 在整张调用树里只第一次被算。之后所有遇到 FkF_{k} 的地方,都查表返回,不再往下展开。

回到第 3 章那棵 F5F_5 的调用树:

                       F(5)
                     /      \
                  F(4)        F(3)
                 /    \      /    \
              F(3)   F(2)  F(2)  F(1)
             /    \   / \   / \
          F(2) F(1) F(1)F(0)F(1)F(0)
          /  \
       F(1) F(0)

加了备忘录之后,调用过程变成:

  1. F5F_5 没在表里,要算 F4F_4 + F3F_3
  2. F4F_4 没在表里,要算 F3F_3 + F2F_2
  3. F3F_3 没在表里,要算 F2F_2 + F1F_1
  4. F2F_2 没在表里,要算 F1F_1 + F0F_0。两个都在表里,返回 1 + 0 = 1。写入 memo[2] = 1。
  5. F1F_1 在表里,返回 1。
  6. F3F_3 算完:1+1=21 + 1 = 2。写入 memo[3] = 2。
  7. F2F_2 在表里,返回 1。
  8. F4F_4 算完:2+1=32 + 1 = 3。写入 memo[4] = 3。
  9. F3F_3 在表里,返回 2。
  10. F5F_5 算完:3+2=53 + 2 = 5。写入 memo[5] = 5。

10 步。原来 15 步。看上去差距不大。但放大到 n = 50,原来是 407 亿步,现在是 51 步。

那棵原来指数膨胀的树,被一张备忘录压扁成一条链。

这种写法有个名字:记忆化

把"算过的就存下来"这个动作加到递归上,叫做记忆化(memoization)。这个英文字生造于 1968 年,是 Donald Michie 提出来的,意思是"把函数已经算过的结果记下来,下次再问就直接给答案"。

记忆化是一个很通用的技巧,不限于斐波那契。任何递归函数,只要满足两条性质,都可以记忆化加速:

函数对同一个输入永远给出同一个输出(没有副作用、没有外部状态)。
在递归展开的过程中,同一个输入会被反复问到。

斐波那契递归满足这两条。所以记忆化一上去,2 Fn+1F_{n+1} 次调用塌缩成 n + 1 次。

记忆化的好处是改动小。原本那段优雅的递归代码不动,只在外面包一层"先查表"的逻辑,整体结构没变。

代价是要花内存。memo 表里要存 n + 1 个数。对斐波那契来说,n 通常不会太大,几十兆就够算到 F106F_{10^6},所以这点内存完全可以接受。

第二步:换个方向,自底向上

记忆化是"自顶向下"的:从 FnF_n 出发,递归到 F0F_0F1F_1,然后一层层算回来。

可以反过来,"自底向上":从 F0F_0F1F_1 出发,一步步往上算到 FnF_n

function F(n):
    if n == 0: return 0
    if n == 1: return 1
    a, b = 0, 1
    for i = 2 to n:
        a, b = b, a + b
    return b

跑一下 n = 5 看看。

初始:a=0,b=1a = 0, b = 1(这就是 F0F_0F1F_1)。
i=2i = 2a,b=1,0+1=1a, b = 1, 0+1 = 1(这就是 F1F_1, F2F_2)。
i=3i = 3a,b=1,1+1=2a, b = 1, 1+1 = 2(这就是 F2F_2, F3F_3)。
i=4i = 4a,b=2,1+2=3a, b = 2, 1+2 = 3(这就是 F3F_3, F4F_4)。
i=5i = 5a,b=3,2+3=5a, b = 3, 2+3 = 5(这就是 F4F_4, F5F_5)。
返回 b = 5。

5 次循环,5 次加法,得到 F5F_5。换成 n = 50,50 次循环。换成 n = 1000,1000 次循环。

时间和记忆化一样是 O(n)O(n),但是空间省了:自底向上版本只用两个变量 a 和 b,O(1)O(1) 空间。记忆化要用 O(n)O(n) 空间存整张表。

为什么自底向上能省空间?因为它不需要"回头"。FkF_{k} 一旦用来算 Fk+1F_{k+1} 之后就再也不需要了。除了 Fk+1F_{k+1} 计算时用到的两个值 FkF_{k}Fk1F_{k-1},其他都不用记。所以只需要两个变量来回倒腾就行。

自顶向下的记忆化不一样,它要把整张表留在那里,因为递归回去的时候可能还要查。其实严格来说,记忆化版本如果改成"只保留最近两项"也是可以的,但写起来没有自底向上那么自然。

这两个写法,本质上做的是同一件事

记忆化和自底向上迭代,从代码看是完全不同的两套写法。一个递归,一个循环。一个用字典,一个用两个变量。

但是从"算了几次加法"这个角度看,它们完全一样:都是 n − 1 次加法,每个 FkF_{k} 算一次,从 k = 2 算到 k = n。

它们都在做同一件事:让每个子问题只被解决一次,然后被复用。

这件事有一个统一的名字:动态规划。

动态规划到底是什么

动态规划这个名字,是 1950 年代 Richard Bellman 起的。这个名字起得不太好,"dynamic" 和 "programming" 都和它实际做的事没什么直接关系。Bellman 自己在回忆录里说,起这个名字主要是为了让资助方听上去觉得时髦,本质上是公关考虑。

抛开名字不谈,动态规划的核心其实就一句话:

如果一个问题可以拆成若干个重叠的子问题,那么把每个子问题的答案记下来,下次再用就直接拿,别再算第二遍。

就这么一句。

这句话里有两个关键词。"重叠"和"记下来"。

"重叠"指的是不同子问题之间会有公共部分。斐波那契递归里 F50F_{50} 需要 F49F_{49}F48F_{48}F49F_{49} 又需要 F48F_{48}F47F_{47}F48F_{48} 是两个父问题的公共子问题。重叠是动态规划起作用的前提。如果子问题完全不重叠(比如归并排序里的两个一半),那每个子问题只被解决一次,根本不需要记忆化,分治就够了。

"记下来"指的是用一张表(或者两个变量、一个数组、一个字典)保存子问题的答案。这是动态规划和朴素递归的区别。朴素递归也算子问题,但算完就丢,所以同一个子问题被反复解决。动态规划算完不丢,所以每个子问题只算一次。

斐波那契是动态规划最简单的样本。它只有一个一维的状态(k 从 0 到 n),状态转移也很简单(Fk=Fk1+Fk2F_{k} = F_{k-1} + F_{k-2})。但它已经把动态规划的两条核心都展示出来了:有重叠子问题,且通过记忆化或自底向上迭代把每个子问题算一次。

动态规划不是背模板

很多人学动态规划的方式,是背一堆模板:背包问题、最长公共子序列、最长上升子序列、编辑距离、矩阵链乘法。每个模板一个转移方程、一组边界条件,套上去就能做。

这种学法的问题在于:它把动态规划当成一堆孤立的技巧,掩盖了它真正的内核。

动态规划的内核其实很简单:识别子问题,发现它们重叠,决定先算哪个,然后算一次就够了。

模板只是这个内核在不同问题上的具体应用。背模板而不懂内核,会做的是模板题。懂了内核,模板只是顺手的事。

我们用斐波那契把内核拆开来看:

  1. 识别子问题。原问题是"算 FnF_n"。子问题是"算 FkF_{k},其中 0 \leq k \leq n"。
  2. 发现重叠。FnF_n 依赖 Fn1F_{n-1}Fn2F_{n-2}Fn1F_{n-1} 又依赖 Fn2F_{n-2}Fn3F_{n-3},所以 Fn2F_{n-2} 被两个父问题需要。重叠出现了。
  3. 决定顺序。从 k = 0 开始往大算,到 k = n 结束。每个 FkF_{k} 只在 Fk1F_{k-1}Fk2F_{k-2} 都已知时才被算。
  4. 算一次。每个 FkF_{k} 在被算出来之后存起来(或丢弃,如果不再需要),下次再用就直接拿。

这四步就是动态规划的全部。模板题里那些复杂的状态定义、奇怪的转移方程,都是这四步在具体问题上的细化。

把动态规划的第一层意思说清楚

到这里可以做一个清楚的判断:

动态规划的核心是一种关于"计算有记忆"的意识。

朴素递归的意识是:每次需要什么就算什么,算完丢掉。
动态规划的意识是:已经算过的东西,不该再付一次代价。

这两种意识之间,差的不只是一张表。差的是对"什么是计算"的根本理解。

朴素递归把计算理解成"按定义展开"。这种理解在数学上是对的,但在工程上不对。展开会出现大量重复,重复会爆炸。

动态规划把计算理解成"按依赖图遍历"。每个子问题是一个节点,依赖关系是边,按拓扑序遍历这张图,每个节点只访问一次。

这两种理解给出的算法,时间复杂度可以差指数级。斐波那契是最干净的例子:朴素递归 Θ(φn)\Theta(\varphi^n),动态规划 Θ(n)\Theta(n)

动态规划的代价

动态规划不是免费的。它用时间换时间,用空间换时间。

空间上,自顶向下要 O(n)O(n) 的备忘录,自底向上虽然能压到 O(1)O(1),但本质上还是"在某个时刻需要保存某些状态"。在更复杂的问题里,状态空间可能很大。背包问题 O(nW)O(nW),矩阵链乘法 O(n2)O(n^2),编辑距离 O(mn)O(mn)。空间常常成为瓶颈。

时间上,虽然每个子问题只算一次,但是子问题的总数本身可能很多。某些问题(比如旅行商)子问题数是指数级的,动态规划只能把"指数的指数"降到"指数",依然不可解。

所以动态规划只在子问题数量可控、且子问题之间确实重叠的时候有用。

对斐波那契来说,子问题数量是 O(n)O(n),重叠是显然的,所以动态规划是最合适的工具。从 Θ(φn)\Theta(\varphi^n) 降到 Θ(n)\Theta(n),这一步已经把朴素递归慢成灾难的问题解决了。

还能更快吗

O(n)O(n) 听起来已经很快了。F106F_{10^6} 用自底向上迭代跑,几毫秒就完事。F109F_{10^9} 跑不动了。这不能怪算法,是数本身太大,存不下来。

但是从算法角度,O(n)O(n) 还能更快。下一章会讲怎么把步子折半,做到 O(logn)O(\log n)

那里的关键,是发现斐波那契递推里藏着一种"翻倍"的结构。Fn+mF_{n+m} 可以从 FnF_n, FmF_{m} 和它们附近的几个值直接算出来,不需要从 F0F_0 一路加到 Fn+mF_{n+m}。这等于说,从 FkF_{k} 跳到 F2kF_{2k} 不需要 k 步加法,只需要常数次乘法和加法。

但是先停下来想一想:从 O(φn)O(\varphi^n)O(n)O(n) 这一步,已经够我们记一辈子了。它告诉我们一件事:

优雅的定义不保证优雅的计算。给计算加上一张备忘录,常常就能把优雅找回来。

斐波那契数列给我们的第三课就是这个:计算是有记忆的,算过的东西不该再付一次代价。

这件事听起来朴素,但它就是动态规划的全部。

下一章我们问:除了"算过的就别再算",还能不能再省一些?答案藏在递推自身的结构里。

第 5 章 快速倍增:从加法到乘法

到上一章为止,我们最好的算法是 O(n)O(n)。要算 FnF_n,从 F0F_0 走到 FnF_n,每一步一次加法,n 步搞定。

这一章问的是:能不能不一步一步走?能不能跳?

答案是可以。斐波那契递推里藏着一种"翻倍"的结构。从 FkF_{k} 跳到 F2kF_{2k},不需要 k 步加法,只需要常数次乘法和加法。这一步把 O(n)O(n) 砍成 O(logn)O(\log n)

先想清楚"翻倍"是什么意思

假设我们已经知道 FkF_{k}Fk+1F_{k+1}。能不能从这两个数直接算出 F2kF_{2k}F2k+1F_{2k+1},而不需要从 Fk+2F_{k+2} 一路加到 F2kF_{2k}

如果能,那从 F0F_0, F1F_1 出发,反复翻倍,就能跳着算到任意 FnF_n

知道 F0F_0, F1F_1,翻一次倍,得到 F0F_0, F1F_1, F2F_2, F3F_3 里的 F2F_2F3F_3
再翻一次倍,从 F2F_2, F3F_3 得到 F4F_4, F5F_5
再翻一次倍,从 F4F_4, F5F_5 得到 F8F_8, F9F_9
再翻一次倍,从 F8F_8, F9F_9 得到 F16F_{16}, F17F_{17}

每一次翻倍,下标翻一倍。所以从 F0F_0, F1F_1 走到 FnF_n,只需要 log2n\log_2 n 次翻倍。

这个思路要工作,关键是有一个公式把 F2kF_{2k}F2k+1F_{2k+1}FkF_{k}Fk+1F_{k+1} 直接写出来。我们下面就把它推出来。

推加法公式

要推翻倍公式,先要推一个更基础的公式:加法公式。它说 Fn+mF_{n+m} 可以用 FnF_nFmF_{m} 附近的几个值表示出来。

我们先猜 Fn+mF_{n+m}FnF_nFmF_{m} 的某种线性组合。也就是说,Fn+m=aFn+1+bFn+cFm+1+dFmF_{n+m} = a F_{n+1} + b F_{n} + c F_{m+1} + d F_{m} 这种形式,其中 a,b,c,da,b,c,d 是常数。

但是这种猜法太宽。我们直接做。

考虑 Fn+mF_{n+m}。固定 mm,把 nn00 开始一个个推:

Fm=FmF_{m} = F_{m}。这是 n=0n=0
Fm+1=Fm+1F_{m+1} = F_{m+1}。这是 n=1n=1
Fm+2=Fm+1+FmF_{m+2} = F_{m+1} + F_{m}。这是 n=2n=2
Fm+3=Fm+2+Fm+1=2Fm+1+FmF_{m+3} = F_{m+2} + F_{m+1} = 2 F_{m+1} + F_{m}
Fm+4=Fm+3+Fm+2=3Fm+1+2FmF_{m+4} = F_{m+3} + F_{m+2} = 3 F_{m+1} + 2 F_{m}
Fm+5=Fm+4+Fm+3=5Fm+1+3FmF_{m+5} = F_{m+4} + F_{m+3} = 5 F_{m+1} + 3 F_{m}

看出来了吗?当 n1n\ge1 时,

Fm+n=FnFm+1+Fn1Fm. F_{m+n}=F_nF_{m+1}+F_{n-1}F_m.

这里先把边界说清楚:这个写法默认 n1n\ge1,避免引入 F1F_{-1}。如果 n=0n=0,公式左边就是 FmF_m,直接单独验证即可。

验证一下。n=5,m=2n = 5, m = 2F7=F5F3+F4F2=52+31=13F_{7} = F_{5} F_{3} + F_{4} F_{2} = 5 \cdot 2 + 3 \cdot 1 = 13。对,F7=13F_{7} = 13
n=4,m=3n = 4, m = 3F7=F4F4+F3F3=33+22=13F_{7} = F_{4} F_{4} + F_{3} F_{3} = 3 \cdot 3 + 2 \cdot 2 = 13。也对。

把下标对称地换一种写法,常用的加法公式是:

Fn+m=Fn+1Fm+FnFm1. F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.

这一式默认 n0,m1n\ge0,m\ge1。如果想让 m=0m=0 也包括进来,就需要额外约定负指标,或者把 m=0m=0 作为边界情形单独验证。这个公式可以数学归纳法证明(对 nn 归纳)。第 14 章会给出它的组合证明。重要的是它给了我们一个工具:Fn+mF_{n+m} 可以从 FnF_n, Fn+1F_{n+1}, FmF_{m}, Fm1F_{m-1} 这四个值直接算出来,不需要从 F0F_0 一路加到 Fn+mF_{n+m}

推翻倍公式

现在令 n=m=kn=m=k,加法公式给出:

F2k=Fk+1Fk+FkFk1=Fk[Fk+1+Fk1]F_{2k} = F_{k+1} F_{k} + F_{k} F_{k-1} = F_{k} [F_{k+1} + F_{k-1}]

但是

Fk+1+Fk1=Fk+1+(Fk+1Fk)=2Fk+1Fk. F_{k+1}+F_{k-1}=F_{k+1}+(F_{k+1}-F_k)=2F_{k+1}-F_k.

最后一个等号用了

Fk1=Fk+1Fk, F_{k-1}=F_{k+1}-F_k,

它可以从 Fk+1=Fk+Fk1F_{k+1}=F_k+F_{k-1} 推出。

所以:

F2k=Fk(2Fk+1Fk)F_{2k}=F_k\cdot(2F_{k+1}-F_k)

这就是 F2kF_{2k} 的翻倍公式。

类似地,令 n=k+1,m=kn=k+1,m=k

F2k+1=Fk+2Fk+Fk+1Fk1. F_{2k+1}=F_{k+2}F_k+F_{k+1}F_{k-1}.

Fk+2=Fk+1+Fk,Fk1=Fk+1Fk, F_{k+2}=F_{k+1}+F_k, \qquad F_{k-1}=F_{k+1}-F_k,

得到

F2k+1=(Fk+1+Fk)Fk+Fk+1(Fk+1Fk)=Fk+1Fk+Fk2+Fk+12Fk+1Fk=Fk2+Fk+12. \begin{aligned} F_{2k+1} &=(F_{k+1}+F_k)F_k+F_{k+1}(F_{k+1}-F_k)\\ &=F_{k+1}F_k+F_k^2+F_{k+1}^2-F_{k+1}F_k\\ &=F_k^2+F_{k+1}^2. \end{aligned}

所以:

F2k+1=Fk2+Fk+12. F_{2k+1}=F_k^2+F_{k+1}^2.

把两个公式写在一起:

F2k=Fk(2Fk+1Fk), F_{2k}=F_k(2F_{k+1}-F_k),

F2k+1=Fk2+Fk+12. F_{2k+1}=F_k^2+F_{k+1}^2.

这两个公式合起来叫"快速倍增公式"。

验证一下

k=3k=3 试一下。F3=2,F4=3F_3=2,F_4=3

按公式:

F6=F3(2F4F3)=2(62)=8F_6=F_3\cdot(2F_4-F_3)=2\cdot(6-2)=8
F7=F32+F42=4+9=13F_7=F_3^2+F_4^2=4+9=13

实际值:F6=8,F7=13F_{6} = 8, F_{7} = 13。对。

再拿 k=5k=5 试一下。F5=5,F6=8F_5=5,F_6=8

F10=F5(2F6F5)=5(165)=55F_{10}=F_5\cdot(2F_6-F_5)=5\cdot(16-5)=55
F11=F52+F62=25+64=89F_{11}=F_5^2+F_6^2=25+64=89

实际值:F10=55,F11=89F_{10} = 55, F_{11} = 89。对。

公式稳。

把它写成算法

用这两个公式,可以从 (F0,F1)=(0,1)(F_0,F_1)=(0,1) 出发,按 nn 的二进制位一步步跳到 (Fn,Fn+1)(F_n,F_{n+1})

具体怎么跳?我们让「当前知道的」始终是一对 (Fk,Fk+1)(F_k,F_{k+1})。从 (F0,F1)(F_0,F_1) 开始,每一步可以选择:

  1. 翻倍:把 (Fk,Fk+1)(F_k,F_{k+1}) 变成 (F2k,F2k+1)(F_{2k},F_{2k+1})。用上面两个公式。
  2. 加一:把 (Fk,Fk+1)(F_k,F_{k+1}) 变成 (Fk+1,Fk+2)(F_{k+1},F_{k+2})。其中 Fk+2=Fk+Fk+1F_{k+2}=F_k+F_{k+1}

要从 k=0k=0 跳到任意 nn,按 nn 的二进制位从高到低走:每一步先翻倍,如果当前二进制位是 1 就再翻加一。这和快速幂的思路完全一样。

举个例子,n=13=11012n=13=1101_2。从 (F0,F1)=(0,1)(F_0,F_1)=(0,1) 出发:

最高位 11:先翻倍(到 k=0×2=0k=0\times 2=0),再加一(到 k=1k=1)。得到 (F1,F2)=(1,1)(F_1,F_2)=(1,1)
下一位 11:先翻倍(到 k=2k=2),再加一(到 k=3k=3)。得到 (F3,F4)=(2,3)(F_3,F_4)=(2,3)
下一位 00:先翻倍(到 k=6k=6),不加一。得到 (F6,F7)=(8,13)(F_6,F_7)=(8,13)
下一位 11:先翻倍(到 k=12k=12),再加一(到 k=13k=13)。得到 (F13,F14)=(233,377)(F_{13},F_{14})=(233,377)

四步,从 (F0F_0, F1F_1) 跳到 (F13F_{13}, F14F_{14})。每步常数次乘法和加法。如果用朴素自底向上,要 13 步。

对一般的 nn,二进制位数是 log2n\log_2 n,所以总共 O(logn)O(\log n) 步。每步常数次大数运算,每次大数运算的时间取决于数的大小,但总时间仍然是 O(logn)O(\log n) 次乘法的代价。

这就是快速倍增

这个算法叫"快速倍增"(fast doubling)。它的核心是:

一对相邻斐波那契数 (FkF_{k}, Fk+1F_{k+1}) 可以被翻倍成 (F2kF_{2k}, F2k+1F_{2k+1})。
nn 的二进制表示控制翻倍和加一,跳着到 (FnF_n, Fn+1F_{n+1})。

时间 O(logn)O(\log n),空间 O(1)O(1)(不算大数存储)。

F1018F_{10^{18}},自底向上要 101810^{18} 步,几亿年。快速倍增在下标层面的递归深度约为 log2(1018)60\log_2(10^{18}) \approx 60 步。

但要小心一个细节。如果只算 F1018modmF_{10^{18}} \bmod m 这种余数,60 步确实足够,因为中间结果不超过 m2m^2。如果要输出精确整数,结果本身约有 2.09×10172.09 \times 10^{17} 位十进制数字(因为 log10φ0.209\log_{10} \varphi \approx 0.209,所以 log10Fn0.209n\log_{10} F_n \approx 0.209 n),不可能瞬间输出。本章后面提到"快速倍增 O(logn)O(\log n)"都是指下标层面的步数,不是指实际输出大整数的时间。

为什么"翻倍"能成立

这件事值得多想一下。

朴素自底向上加法,是从 FkF_{k} 加到 Fk+1F_{k+1} 一步一步走。每一步只看相邻两项,所以每次只能前进 1。

快速倍增能跳,是因为它用了一种"远距离"的依赖关系。具体来说,加法公式 Fn+mF_{n+m} = Fn+1F_{n+1} FmF_{m} + FnF_n Fm1F_{m-1} 告诉我们:Fn+mF_{n+m} 不只是 Fn+m1F_{n+m-1}Fn+m2F_{n+m-2} 的和,它还可以用 FnF_nFmF_{m} 的乘积表示。

这意味着斐波那契数之间有一种"全局"的乘积关系。这种乘积关系允许我们把 k 步加法压缩成 1 次乘法。

为什么会有这种乘积关系?因为斐波那契递推是线性的。线性递推的解可以写成若干个 rnr^{n} 的线性组合,其中 r 是特征方程的根。这意味着 Fn+mF_{n+m} 中的 (n+m) 可以拆成 n 和 m,分别用 rnr^{n}rmr^{m} 表示。所以乘积形式是自然的。

这个观察在第 8 章(闭式公式)和尾声(一般线性递推)里会再次出现。这里只是先让你看到:"线性"这个性质不只是说递推式里没有 Fn2F_n^2 之类的项,它还隐含着一系列乘积恒等式,而这些恒等式让我们可以跳步。

加法变乘法,是结构允许跳步

到这里可以总结一下快速倍增的精髓。

朴素自底向上已经很聪明了。它发现子问题重叠,所以每个子问题只算一次。但是它只用了"局部"的递推关系:FkF_{k} 依赖 Fk1F_{k-1}Fk2F_{k-2}

快速倍增更进一步。它发现了"全局"的乘积关系:Fn+mF_{n+m} 可以用 FnF_nFmF_{m} 表示。所以 F2kF_{2k} 不需要 k 步加法,只需要 1 次乘法。

把加法变成乘法,本质上是利用了递推的线性结构。线性带来可分性,可分性带来跳步。

这是斐波那契给我们的第四个教训。它告诉我们:算法的快慢,不只取决于代码写得多精巧,还取决于我们对底层结构的利用有多深。

一个小尾巴:再加一道恒等式

最后再补一个快速倍增会用到的小技巧。翻倍之后要做"加一",从 (FkF_{k}, Fk+1F_{k+1}) 变到 (Fk+1F_{k+1}, Fk+2F_{k+2})。Fk+2=Fk+1+FkF_{k+2} = F_{k+1} + F_{k}是显然的。

但是有时候我们也想从 (FkF_{k}, Fk+1F_{k+1}) 直接跳到 (F2k+1F_{2k+1}, F2k+2F_{2k+2}),跳两格而不是一格。这种情况怎么算?

F2k+2=F2k+1+F2kF_{2k+2} = F_{2k+1} + F_{2k},两个值都已经在翻倍公式里算出来了,加一下就行。

更一般地,所有 FnF_n 都可以通过"翻倍 + 一步前进"组合出来。具体的实现细节这里不展开,留到下一章讲完矩阵快速幂之后会看得更清楚。矩阵的乘法天然地把"翻倍"和"前进"统一成同一种操作。

下一章我们换一种工具看同一个跳步结构。不再用代数恒等式,改用矩阵幂。结果会发现,快速倍增公式其实是矩阵平方的展开版。

第 6 章 矩阵快速幂

上一章我们用快速倍增把斐波那契做到了 O(logn)O(\log n)。这一章换一种工具看同一个跳步结构:矩阵快速幂。

矩阵在斐波那契里的位置很有意思。第一次见它的人会觉得"为什么要扯上矩阵",但是看清楚之后会发现,矩阵只是把快速倍增的代数恒等式换了一种紧凑的写法。

把递推写成矩阵

回忆斐波那契递推:Fn+1=Fn+Fn1F_{n+1} = F_n + F_{n-1}

把它和恒等式 Fn=FnF_n = F_n 凑在一起,可以写成:

(Fn+1Fn)=(Fn+Fn1Fn)=(1110)(FnFn1). \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} = \begin{pmatrix} F_n + F_{n-1} \\ F_n \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} F_n \\ F_{n-1} \end{pmatrix}.

验算一下右边。第一行:1Fn+1Fn1=Fn+Fn1=Fn+11 \cdot F_n + 1 \cdot F_{n-1} = F_n + F_{n-1} = F_{n+1}。对。
第二行:1Fn+0Fn1=Fn1 \cdot F_n + 0 \cdot F_{n-1} = F_n。对。

把中间那个 2×22 \times 2 矩阵记作 MM

M=(1110). M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}.

那么递推可以写成 v(n+1)=Mv(n)v(n+1) = M \cdot v(n),其中 v(n)=(Fn+1Fn)v(n) = \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}

反复用这个关系,从 v(0)=(F1F0)=(10)v(0) = \begin{pmatrix} F_1 \\ F_0 \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \end{pmatrix} 出发,得到:

v(n)=Mv(n1)=M2v(n2)==Mnv(0). v(n) = M \cdot v(n-1) = M^2 \cdot v(n-2) = \cdots = M^n \cdot v(0).

也就是说:

(Fn+1Fn)=Mn(10). \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} = M^n \begin{pmatrix} 1 \\ 0 \end{pmatrix}.

FnF_n 等价于算 MnM^n,然后看第二项。

这一步把"算斐波那契"和"算矩阵幂"对等起来了。任何能快速算 MnM^n 的算法,都能快速算 FnF_n

矩阵怎么帮忙跳步

MnM^{n} 听起来好像更复杂了。算一个数的 n 次幂已经够烦,算矩阵的 n 次幂不是更烦?

关键在于:算 ana^{n}(普通数的幂)有快速幂算法,O(logn)O(\log n) 次乘法搞定。同样的算法可以搬到矩阵上,得到矩阵快速幂。

普通数的快速幂是这样的:

  • a0=1a^0=1
  • a1=aa^1=a
  • a2k=(ak)2a^{2k}=(a^k)^2
  • a2k+1=aa2ka^{2k+1}=a\cdot a^{2k}

nn 的二进制位从低到高走,每遇到一个 11 就把当前平方过的 a2ia^{2^i} 乘进结果。总共 O(logn)O(\log n) 次乘法。

矩阵快速幂完全一样,只是把「数 aa」换成「矩阵 MM」,把"乘法"换成"矩阵乘法":

  • M0=IM^0=I(单位矩阵)
  • M1=MM^1=M
  • M2k=(Mk)2M^{2k}=(M^k)^2
  • M2k+1=MM2kM^{2k+1}=M\cdot M^{2k}

nn 的二进制位走,O(logn)O(\log n) 次矩阵乘法。每次矩阵乘法是 2×22 \times 2 矩阵相乘,8 次乘法和 4 次加法。

所以矩阵快速幂算 FnF_n 的总时间是 O(logn)O(\log n) 次大数运算,每次大数运算包含常数次乘法和加法。和快速倍增一样是 O(logn)O(\log n)

跑一下 n=13n=13

用矩阵快速幂算 F13F_{13}n=13=11012n=13=1101_2。从结果 res=I\mathrm{res}=I(单位矩阵),base=M\mathrm{base}=M 开始,按二进制位从低到高走:

最低位 1:res = res · base = I · M = M。base = base 的平方 = M2M^{2}
下一位 0:res 不变。base = base 的平方 = M4M^{4}
下一位 1:res = res · base = M · M4M^{4} = M5M^{5}。base = base 的平方 = M8M^{8}
最高位 1:res = res · base = M5M^{5} · M8M^{8} = M13M^{13}

4 步矩阵乘法(不算单位矩阵那次),得到 M13M^{13}。然后 F13F_{13} = M13M^{13} 的第二行第一列(或第一行第二列,下面会看到两个都行)。

具体值 M13M^{13} 等于什么?我们下一章会证 Mn=(Fn+1FnFnFn1)M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix},所以 M13=(F14F13F13F12)=(377233233144)M^{13} = \begin{pmatrix} F_{14} & F_{13} \\ F_{13} & F_{12} \end{pmatrix} = \begin{pmatrix} 377 & 233 \\ 233 & 144 \end{pmatrix}。读出 F13=233F_{13} = 233

对。

矩阵快速幂和快速倍增是同一件事

到这里你可能会想:矩阵快速幂和快速倍增,到底有什么关系?

它们做的事情完全一样。都是用 n 的二进制位控制翻倍,O(logn)O(\log n) 步搞定。

更精确地说,快速倍增的两个公式:

F2k=Fk(2Fk+1Fk),F2k+1=Fk2+Fk+12. F_{2k} = F_k \cdot (2 F_{k+1} - F_k), \qquad F_{2k+1} = F_k^2 + F_{k+1}^2.

可以从矩阵平方 M2k=(Mk)2M^{2k} = (M^k)^2 直接推出来。

我们算一下 (Mk)2(M^k)^2。假设 Mk=(abbc)M^k = \begin{pmatrix} a & b \\ b & c \end{pmatrix}(这个形式下一章会证,所有 MnM^n 都是对称的,且 entry 是斐波那契数)。那么:

(Mk)2=(abbc)(abbc)=(a2+b2ab+bcab+bcb2+c2)=(a2+b2b(a+c)b(a+c)b2+c2). (M^k)^2 = \begin{pmatrix} a & b \\ b & c \end{pmatrix} \cdot \begin{pmatrix} a & b \\ b & c \end{pmatrix} = \begin{pmatrix} a^2 + b^2 & ab + bc \\ ab + bc & b^2 + c^2 \end{pmatrix} = \begin{pmatrix} a^2 + b^2 & b(a+c) \\ b(a+c) & b^2 + c^2 \end{pmatrix}.

M2kM^{2k} 也具有相同的形式 (F2k+1F2kF2kF2k1)\begin{pmatrix} F_{2k+1} & F_{2k} \\ F_{2k} & F_{2k-1} \end{pmatrix}。所以:

F2k+1=a2+b2=Fk+12+Fk2,F2k=b(a+c)=Fk(Fk+1+Fk1)=Fk(2Fk+1Fk). \begin{aligned} F_{2k+1} &= a^2 + b^2 = F_{k+1}^2 + F_k^2, \\ F_{2k} &= b(a+c) = F_k \cdot (F_{k+1} + F_{k-1}) = F_k \cdot (2 F_{k+1} - F_k). \end{aligned}

第二个等号用了 Fk1=Fk+1FkF_{k-1} = F_{k+1} - F_k

这就是快速倍增公式。它从矩阵平方 M2k=(Mk)2M^{2k} = (M^k)^2 直接掉出来。

所以快速倍增其实是矩阵快速幂的"展开版"。它把 2×22 \times 2 矩阵乘法里 8 次乘法 4 次加法,简化成了 3 次乘法 2 次加法(利用了 MnM^n 的对称性,省掉了一些项)。常数小一点,但本质上是同一件事。

为什么用矩阵快速幂

既然快速倍增常数更小,为什么还要讲矩阵快速幂?

两个理由。

第一,矩阵形式更紧凑。MnM^{n} 一个符号就表达了"翻 n 次状态转移"这件事,不需要写两个公式。在很多场合,紧凑的表达比省一两次乘法更重要。

第二,矩阵形式更通用。斐波那契的伴随矩阵 MM(1110)\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix},但其他线性递推有其他伴随矩阵。比如 Pell 数列 Pn=2P(n1)+P(n2)P_n = 2 P(n-1) + P(n-2) 的伴随矩阵是 (2110)\begin{pmatrix} 2 & 1 \\ 1 & 0 \end{pmatrix}。一旦学会矩阵快速幂,所有二阶线性递推都可以套同一套算法,只是矩阵 entry 换一下。

更重要的是,矩阵形式让"线性递推"和"线性变换迭代"对应起来。学会这种对应之后,看见任何线性递推,第一反应都是"它的伴随矩阵是什么"。这是第 7 章的主题。

一个具体的代码对比

让我们把两个算法的代码放在一起对比一下。

快速倍增(递归版):

function fast_doubling(n):
    if n == 0: return (0, 1)
    a, b = fast_doubling(n // 2)
    c = a * (2 * b - a)
    d = a * a + b * b
    if n % 2 == 0:
        return (c, d)
    else:
        return (d, c + d)

矩阵快速幂(迭代版):

function mat_mul(A, B):
    return [[A[0][0]*B[0][0] + A[0][1]*B[1][0], A[0][0]*B[0][1] + A[0][1]*B[1][1]],
            [A[1][0]*B[0][0] + A[1][1]*B[1][0], A[1][0]*B[0][1] + A[1][1]*B[1][1]]]

function mat_pow(n):
    result = [[1, 0], [0, 1]]   # 单位矩阵
    base = [[1, 1], [1, 0]]
    while n > 0:
        if n % 2 == 1:
            result = mat_mul(result, base)
        base = mat_mul(base, base)
        n = n // 2
    return result

两个算法都跑 O(logn)O(\log n) 次循环。快速倍增每次循环 3 次乘法 2 次加法。矩阵快速幂每次循环 8 次乘法 4 次加法(如果 result 也要更新就是 16 次乘法 8 次加法)。

所以快速倍增的常数确实小。但是矩阵快速幂的代码更整齐:所有操作都是"矩阵乘矩阵",没有快速倍增里那种"2 Fk+1F_{k+1} - FkF_{k}"看起来有点神秘的公式。

写代码的时候,如果你只关心斐波那契,用快速倍增。如果你要处理一般线性递推,或者要和第 7 章的状态视角接上,用矩阵快速幂。

本章小结

我们把斐波那契递推改写成了矩阵形式 v(n+1) = M · v(n),然后把"算 FnF_n"对等到"算 MnM^{n}"。

矩阵快速幂用和普通快速幂完全一样的二进制分解思路,O(logn)O(\log n) 次矩阵乘法搞定 MnM^{n}

快速倍增和矩阵快速幂本质上是同一件事。快速倍增是矩阵平方 (Mk)2(M^k)^2 在斐波那契特殊情况下的展开版,常数小一点。

矩阵形式更紧凑、更通用。任何二阶线性递推都有自己的伴随矩阵,矩阵快速幂可以套用到所有这些递推上。

到这一章为止,我们已经把"算 FnF_n"从 O(φn)O(\varphi^n) 一路压到 O(logn)O(\log n)。每一步都来自对递推结构的更深入利用:第 4 章发现子问题重叠,第 5 章发现线性结构带来乘积恒等式,这一章发现矩阵把乘积恒等式压缩成一个统一的运算。

但是矩阵在斐波那契里的角色,远不止"加速算法"这一项。下一章我们会把矩阵拿来当一种语言看:让矩阵从"加速工具"变成"状态变化的描述"。我们会发现,矩阵不是凭空出现的表格,它可以从"递推要继续,到底要记住什么"这个最朴素的问题里长出来。

第 7 章 矩阵作为状态语言

上一章我们用矩阵快速幂算 FnF_n。矩阵 MM 在那里只是一个工具,我们没多想它为什么是那个样子,为什么刚好 (1110)\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}

这一章换一个视角:让矩阵从"工具"变成"语言"。我们要从"递推要继续,到底要记住什么"这个最朴素的问题开始,让矩阵自己走出来。

递推要继续,到底要记住什么

斐波那契递推说:Fn=Fn1+Fn2F_{n} = F_{n-1} + F_{n-2}

我们要从第 n-1 步推到第 n 步。这个动作需要什么信息?

不是 Fn1F_{n-1} 这一个数。是 Fn1F_{n-1}Fn2F_{n-2} 两个数。只有 Fn1F_{n-1} 不够。算 FnF_nFn2F_{n-2},而 Fn2F_{n-2} 在算 Fn1F_{n-1} 的时候被"用掉"了,如果没有专门记下来,就丢了。

也就是说,斐波那契递推的状态不是一个数,是一对数:(Fn1,Fn2)(F_{n-1}, F_{n-2})。或者等价地写成 (Fn,Fn1)(F_n, F_{n-1}),后一项和当前项。

这一点是关键。它解释了为什么斐波那契不像"前一项加 1"那么简单。一维状态推不下去,状态本身就是二维的。

让状态向量是 v(n)=(Fn+1Fn)v(n) = \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}

那从 v(n)v(n)v(n+1)v(n+1) 是什么?要把 (Fn+1Fn)\begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} 变成 (Fn+2Fn+1)\begin{pmatrix} F_{n+2} \\ F_{n+1} \end{pmatrix}

按定义 Fn+2=Fn+1+FnF_{n+2} = F_{n+1} + F_n。所以新的第一项是 Fn+1+FnF_{n+1} + F_n,新的第二项是 Fn+1F_{n+1}

写成向量:

(Fn+2Fn+1)=(Fn+1+FnFn+1)=(1110)(Fn+1Fn). \begin{pmatrix} F_{n+2} \\ F_{n+1} \end{pmatrix} = \begin{pmatrix} F_{n+1} + F_n \\ F_{n+1} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix}.

中间那个 (1110)\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} 就是把 (Fn+1,Fn)(F_{n+1}, F_n) 变成 (Fn+2,Fn+1)(F_{n+2}, F_{n+1}) 的"动作"。

记它为 MM

M=(1110). M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}.

那么 v(n+1)=Mv(n)v(n+1) = M \cdot v(n)

这就是斐波那契递推的矩阵形式。

矩阵是什么?这里它就是一个动作

我们刚才做的事情是:把"从一步到下一步"这个动作,写成一个矩阵乘法。

这一点要慢慢体会。矩阵 M=(1110)M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} 在这里不是一组随便摆放的数。它是一个"动作"。这个动作把状态向量从 (Fn+1,Fn)(F_{n+1}, F_n) 推到 (Fn+2,Fn+1)(F_{n+2}, F_{n+1})

矩阵的每一行,告诉你新状态的每一项是怎么从旧状态组合出来的:

第一行 [1,1][1, 1]:新状态的第一项 =1Fn+1+1Fn=Fn+1+Fn=Fn+2= 1 \cdot F_{n+1} + 1 \cdot F_n = F_{n+1} + F_n = F_{n+2}。这是定义。
第二行 [1,0][1, 0]:新状态的第二项 =1Fn+1+0Fn=Fn+1= 1 \cdot F_{n+1} + 0 \cdot F_n = F_{n+1}。这是把上一项"挪下来"。

所以矩阵的两行,分别对应两个动作:第一行做"加和",把前两项加起来变成下一项。第二行做"挪位",把当前项变成历史项。

这两件事加起来,就是斐波那契递推的全部动作。矩阵把它压缩到 4 个数里,但每个数都有意义。

这就是矩阵在斐波那契里出现的方式。它从"我们要记住什么、怎么变到下一步"这个需求里长出来。

把它乘 n 次看看

既然 v(n+1)=Mv(n)v(n+1) = M \cdot v(n),那 v(n)=Mv(n1)=M2v(n2)==Mnv(0)v(n) = M \cdot v(n-1) = M^2 \cdot v(n-2) = \cdots = M^n \cdot v(0)

初始向量 v(0)=(F1F0)=(10)v(0) = \begin{pmatrix} F_1 \\ F_0 \end{pmatrix} = \begin{pmatrix} 1 \\ 0 \end{pmatrix}。所以:

(Fn+1Fn)=Mn(10). \begin{pmatrix} F_{n+1} \\ F_n \end{pmatrix} = M^n \begin{pmatrix} 1 \\ 0 \end{pmatrix}.

这个等式说:算 FnF_n 等价于算 MnM^n 然后看第二项。

到这一步,矩阵形式已经做了两件事:

把斐波那契递推改写成了一个"线性变换的幂"。
把"算 FnF_n"和"算 MnM^n"对等起来。

第二件事意味着,任何能快速算 MnM^n 的算法,都能快速算 FnF_n。这是上一章矩阵快速幂的入口。

但更重要的是第一件事:递推被改写成"线性变换迭代 nn 次"。这种改写揭示了斐波那契递推的某种代数结构。下面我们看这种结构能告诉我们什么。

看看 MnM^n 长什么样

MM 的前几次幂算一下:

M1=(1110), M^1 = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix},

M2=MM=(11+1111+1011+0111+00)=(2111), M^2 = M \cdot M = \begin{pmatrix} 1\cdot 1+1\cdot 1 & 1\cdot 1+1\cdot 0 \\ 1\cdot 1+0\cdot 1 & 1\cdot 1+0\cdot 0 \end{pmatrix} = \begin{pmatrix} 2 & 1 \\ 1 & 1 \end{pmatrix},

M3=M2M=(21+1121+1011+1111+10)=(3221), M^3 = M^2 \cdot M = \begin{pmatrix} 2\cdot 1+1\cdot 1 & 2\cdot 1+1\cdot 0 \\ 1\cdot 1+1\cdot 1 & 1\cdot 1+1\cdot 0 \end{pmatrix} = \begin{pmatrix} 3 & 2 \\ 2 & 1 \end{pmatrix},

M4=M3M=(5332),M5=(8553). M^4 = M^3 \cdot M = \begin{pmatrix} 5 & 3 \\ 3 & 2 \end{pmatrix}, \qquad M^5 = \begin{pmatrix} 8 & 5 \\ 5 & 3 \end{pmatrix}.

看出来了吗?MnM^n 总是这种形式:

Mn=(Fn+1FnFnFn1). M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix}.

这个形式可以数学归纳法证明。

n=1n = 1 时,M1=(1110)=(F2F1F1F0)M^1 = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} F_2 & F_1 \\ F_1 & F_0 \end{pmatrix}。对。

假设 Mk=(Fk+1FkFkFk1)M^k = \begin{pmatrix} F_{k+1} & F_k \\ F_k & F_{k-1} \end{pmatrix}。那 Mk+1=MkMM^{k+1} = M^k \cdot M

Mk+1=(Fk+1FkFkFk1)(1110)=(Fk+1+FkFk+1Fk+Fk1Fk)=(Fk+2Fk+1Fk+1Fk). M^{k+1} = \begin{pmatrix} F_{k+1} & F_k \\ F_k & F_{k-1} \end{pmatrix} \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} F_{k+1}+F_k & F_{k+1} \\ F_k+F_{k-1} & F_k \end{pmatrix} = \begin{pmatrix} F_{k+2} & F_{k+1} \\ F_{k+1} & F_k \end{pmatrix}.

确实等于 (Fk+2Fk+1Fk+1Fk)\begin{pmatrix} F_{k+2} & F_{k+1} \\ F_{k+1} & F_k \end{pmatrix}。归纳成立。

所以 MnM^n 的四个 entry 都是斐波那契数。这给出了一组恒等式。

这件事本身就很妙:一个看起来毫无关系的矩阵,反复自乘之后,每个位置上都是一个斐波那契数。而且位置有讲究,它反映了斐波那契数之间的对称性。

矩阵形式立刻给出恒等式

既然 MnM^n 的形式固定,那 MnMm=Mn+mM^n \cdot M^m = M^{n+m} 就给出斐波那契恒等式。

具体算一下。Mn=(Fn+1FnFnFn1)M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix}Mm=(Fm+1FmFmFm1)M^m = \begin{pmatrix} F_{m+1} & F_m \\ F_m & F_{m-1} \end{pmatrix}。它们的乘积等于 Mn+m=(Fn+m+1Fn+mFn+mFn+m1)M^{n+m} = \begin{pmatrix} F_{n+m+1} & F_{n+m} \\ F_{n+m} & F_{n+m-1} \end{pmatrix}

把左边乘开:

MnMm=(Fn+1Fm+1+FnFmFn+1Fm+FnFm1FnFm+1+Fn1FmFnFm+Fn1Fm1). M^n \cdot M^m = \begin{pmatrix} F_{n+1} F_{m+1} + F_n F_m & F_{n+1} F_m + F_n F_{m-1} \\ F_n F_{m+1} + F_{n-1} F_m & F_n F_m + F_{n-1} F_{m-1} \end{pmatrix}.

对比右边 (Fn+m+1Fn+mFn+mFn+m1)\begin{pmatrix} F_{n+m+1} & F_{n+m} \\ F_{n+m} & F_{n+m-1} \end{pmatrix},得到:

  • 左上角:Fn+1Fm+1+FnFm=Fn+m+1F_{n+1} F_{m+1} + F_n F_m = F_{n+m+1}
  • 右上角:Fn+1Fm+FnFm1=Fn+mF_{n+1} F_m + F_n F_{m-1} = F_{n+m}
  • 左下角:FnFm+1+Fn1Fm=Fn+mF_n F_{m+1} + F_{n-1} F_m = F_{n+m}
  • 右下角:FnFm+Fn1Fm1=Fn+m1F_n F_m + F_{n-1} F_{m-1} = F_{n+m-1}

这就是从矩阵形式直接掉出来的恒等式。其中右上角那个就是我们第 5 章用过的加法公式:

Fn+m=Fn+1Fm+FnFm1. F_{n+m} = F_{n+1} F_m + F_n F_{m-1}.

当时我们说这个公式"成立",但是要单独验证。现在从矩阵来看,它根本就是矩阵乘法的必然结果。MnMmM^n \cdot M^m 必须等于 Mn+mM^{n+m},所以右上角的 entry 必须给出 Fn+mF_{n+m} 的乘积公式。

矩阵在这里做了一件事:把若干个看似独立的恒等式,统一成同一个事实的不同 entry。这是矩阵作为"语言"的威力。它把零散的代数恒等式压缩成一个紧凑的几何结构。

矩阵作为"状态变化的语言"

这一章的核心,可以总结成一句话:

矩阵可以从"我要记住两个数"这个朴素需求里长出来。它是状态变化的语言。

很多人对矩阵的第一印象是"一个矩形数组"。这个印象没错,但也没用。它没告诉你矩阵为什么重要,没告诉你为什么要做矩阵乘法这种奇怪的操作。

矩阵真正有力的地方,是它能把"一个状态怎么变成下一个状态"这种动作压缩进一个紧凑的对象里。状态是一个向量,动作是一个矩阵,状态变化就是向量乘矩阵。如果同一个动作反复施加,那就是矩阵的幂。

斐波那契是这种视角的最简单样本。状态是二维向量 (Fn+1,Fn)(F_{n+1}, F_n),动作是矩阵 M=(1110)M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix},状态变化就是 v(n+1)=Mv(n)v(n+1) = M \cdot v(n)FnF_nMnM^n 的一个 entry。

这种视角的好处是它天然地推广。任何"线性递推",状态在每一步是前几步的线性组合,都可以写成矩阵形式。一旦写成矩阵形式,就可以用矩阵幂来快速计算,可以用特征值来研究增长,可以用模运算来研究周期。

第 5 章我们用矩阵快速幂做了加速算法。这一章我们把矩阵拿来当语言。同一个 MM,两种用法,对应两种视角。

矩阵从"我要记住两个数"这个朴素需求里走出来,最终变成了一种通用的语言。下一章我们看这种语言如何自然地把 5\sqrt{5} 引出来。

第 8 章 闭式公式与 5\sqrt5 的来历

到上一章为止,我们一直在做一件事:把 FnF_n 算出来。朴素递归、记忆化、自底向上、快速倍增、矩阵快速幂,所有这些算法,本质上都是按某种顺序做加法和乘法,逐步逼近 FnF_n

这一章换一个问题:能不能不用递推,而是一步写出 FnF_n?也就是说,能不能有一个只含 nn 的公式,把 nn 代进去就得到答案?

这种公式叫闭式公式。它的意思是:FnF_n 可以用 nn 的有限次运算直接表示,不需要循环,不需要递归,不需要先算前面的项。

斐波那契数列确实有闭式公式,而且这个公式里出现了 5\sqrt5。一个由整数加法生成的数列,闭式公式里竟然有一个无理数。第一次看到这件事,很多人会觉得诧异。这一章就把 5\sqrt5 的来源讲清楚。

先猜一个形状

斐波那契递推是

Fn=Fn1+Fn2. F_n=F_{n-1}+F_{n-2}.

这是线性递推。线性递推有一个常见试探:先看有没有形如 rnr^n 的解。于是先假设

Fn=Arn, F_n=A r^n,

其中 AA 是常数,rr 是待定的数。

把它代入递推:

Arn=Arn1+Arn2. A r^n=A r^{n-1}+A r^{n-2}.

两边除以 Arn2A r^{n-2},得到

r2=r+1. r^2=r+1.

也就是

r2r1=0. r^2-r-1=0.

这个二次方程的两个根是

r=1±52. r=\frac{1\pm\sqrt5}{2}.

记作

φ=1+52,ψ=152. \varphi=\frac{1+\sqrt5}{2}, \qquad \psi=\frac{1-\sqrt5}{2}.

φ\varphi 是常说的黄金比例,ψ\psi 是它的另一个共轭根,约为 0.618-0.618

到这里,5\sqrt5 已经出现了。它是方程 r2r1=0r^2-r-1=0 的判别式带来的结果。

用初值定出系数

因为 φn\varphi^nψn\psi^n 都满足同一个递推,它们的线性组合也满足递推。因此一般形式可以写成

Fn=Aφn+Bψn. F_n=A\varphi^n+B\psi^n.

用初值 F0=0F_0=0F1=1F_1=1 来确定 A,BA,B

n=0n=0 时:

F0=A+B=0, F_0=A+B=0,

所以 B=AB=-A

n=1n=1 时:

F1=Aφ+Bψ=1. F_1=A\varphi+B\psi=1.

代入 B=AB=-A,得到

A(φψ)=1. A(\varphi-\psi)=1.

又因为

φψ=5, \varphi-\psi=\sqrt5,

所以

A=15,B=15. A=\frac1{\sqrt5}, \qquad B=-\frac1{\sqrt5}.

于是得到 Binet 公式:

Fn=φnψn5. F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.

φ\varphiψ\psi 展开,就是

Fn=(1+52)n(152)n5. F_n= \frac{ \left(\frac{1+\sqrt5}{2}\right)^n- \left(\frac{1-\sqrt5}{2}\right)^n }{\sqrt5}.

小例子检查

n=0n=0 时,

F0=115=0. F_0=\frac{1-1}{\sqrt5}=0.

n=1n=1 时,

F1=φψ5=1. F_1=\frac{\varphi-\psi}{\sqrt5}=1.

n=2n=2 时,利用 φ2=φ+1\varphi^2=\varphi+1ψ2=ψ+1\psi^2=\psi+1,有

F2=φ2ψ25=(φ+1)(ψ+1)5=1. F_2= \frac{\varphi^2-\psi^2}{\sqrt5} =\frac{(\varphi+1)-(\psi+1)}{\sqrt5} =1.

n=10n=10 时,

F10=φ10ψ105=55. F_{10}=\frac{\varphi^{10}-\psi^{10}}{\sqrt5}=55.

如果用小数近似计算,φ10122.992\varphi^{10}\approx122.992ψ100.00813\psi^{10}\approx0.00813,两者相减后再除以 5\sqrt5,结果约为 5555

5\sqrt5 为什么会消失

Binet 公式里同时出现了 +5+\sqrt55-\sqrt5

φ=1+52,ψ=152. \varphi=\frac{1+\sqrt5}{2}, \qquad \psi=\frac{1-\sqrt5}{2}.

(1+5)n(1+\sqrt5)^n 用二项式定理展开:

(1+5)n=k=0n(nk)(5)k. (1+\sqrt5)^n = \sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.

\sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.

偶数次项是有理数,奇数次项是 5\sqrt5 的有理数倍。(15)n(1-\sqrt5)^n 的偶数次项相同,奇数次项符号相反。

所以

(1+5)n(15)n (1+\sqrt5)^n-(1-\sqrt5)^n

会把偶数次项抵消,只留下奇数次项。留下的每一项都带一个 5\sqrt5,再除以 Binet 公式分母里的 5\sqrt5,就回到了有理数。第 9 章会进一步说明它为什么恰好是整数。

黄金比例在这里是什么角色

φ\varphi 和斐波那契数列确实关系密切。Binet 公式告诉我们,FnF_n 的主导项是

φn5. \frac{\varphi^n}{\sqrt5}.

因为 ψ<1|\psi|<1,当 nn 变大时,ψn\psi^n 会迅速接近 00。所以 FnF_n 越来越接近 φn/5\varphi^n/\sqrt5

但这里要小心:φ\varphi 出现,是因为它是方程

r2=r+1 r^2=r+1

的较大根。它不是神秘装饰,也不是从几何里硬贴过来的常数。换一个二阶线性递推,特征方程变了,特征根也会变。

比如

an=3an12an2 a_n=3a_{n-1}-2a_{n-2}

的特征方程是

r2=3r2, r^2=3r-2,

也就是

r23r+2=0. r^2-3r+2=0.

它的根是 1122,不会出现黄金比例。

矩阵视角下的同一件事

上一章的状态转移矩阵是

M=(1110). M= \begin{pmatrix} 1&1\\ 1&0 \end{pmatrix}.

它的特征值来自

det(MλI)=0. \det(M-\lambda I)=0.

计算得到

det(MλI)=1λ11λ=λ2λ1. \det(M-\lambda I) = \begin{vmatrix} 1-\lambda&1\\ 1&-\lambda \end{vmatrix} = \lambda^2-\lambda-1.

\begin{vmatrix}
1-\lambda&1\
1&-\lambda
\end{vmatrix}

\lambda^2-\lambda-1.

所以 MM 的两个特征值正是 φ\varphiψ\psi

如果把 MM 对角化成

M=PDP1,D=(φ00ψ), M=PDP^{-1}, \qquad D= \begin{pmatrix} \varphi&0\\ 0&\psi \end{pmatrix},

那么

Mn=PDnP1=P(φn00ψn)P1. M^n=PD^nP^{-1} = P \begin{pmatrix} \varphi^n&0\\ 0&\psi^n \end{pmatrix} P^{-1}.

P
\begin{pmatrix}
\varphi^n&0\
0&\psi^n
\end{pmatrix}
P^{-1}.

Binet 公式就是这个矩阵幂公式展开后的结果。把「反复乘 MM」改写成「对特征值取幂」,闭式公式就出现了。

5\sqrt5 在矩阵视角里也有很具体的位置:它是两个特征值的差,

φψ=5. \varphi-\psi=\sqrt5.

P1P^{-1} 时会除以这个差,所以 Binet 公式的分母里出现了 5\sqrt5

为什么不用 Binet 公式精确计算大数

理论上,Binet 公式看起来是直接代入:

Fn=φnψn5. F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.

但对大 nn 来说,精确计算并不适合这样做。原因有两个。

第一,φ\varphi 是无理数,计算机只能用有限精度近似它。双精度浮点数大约只有 15151616 位有效数字。比如

F100=354224848179261915075, F_{100}=354224848179261915075,

已经有 2121 位十进制数字,普通浮点数无法精确表示。

第二,真正输出 FnF_n 时,结果本身会很长。即使用闭式公式,结果的位数成本也不会消失。

所以 Binet 公式的主要价值在于理解结构。它告诉我们:

  • FnF_n 的增长速度由 φn\varphi^n 控制;
  • 5\sqrt5 来自特征方程;
  • 一般线性递推也可以用特征根分析。

实际精确计算大 FnF_n,仍然更适合用快速倍增或矩阵快速幂。它们使用整数运算,时间复杂度是 O(logn)O(\log n),不会引入浮点误差。

本章小结

我们想找一个一步到位的公式。假设 FnF_n 形如 rnr^n,得到特征方程

r2=r+1. r^2=r+1.

这个方程的两个根是 φ\varphiψ\psi。用初值确定系数后,得到 Binet 公式

Fn=φnψn5. F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.

5\sqrt5 出现得很具体:它来自特征方程的判别式,也等于两个特征值的差。下一章我们继续看另一件事:公式里充满无理数,结果为什么总是整数。

第 9 章 5\sqrt5 怎么消失:从无理数回到整数

上一章推出了 Binet 公式:

Fn=φnψn5,φ=1+52,ψ=152. F_n=\frac{\varphi^n-\psi^n}{\sqrt5}, \qquad \varphi=\frac{1+\sqrt5}{2}, \qquad \psi=\frac{1-\sqrt5}{2}.

公式里有 5\sqrt5,但 FnF_n 永远是整数。这一章专门讲这件事:无理数是怎样进来的,又怎样在结果里消失。

先列出 φ\varphiψ\psi 的性质

由定义可得:

φ+ψ=1,φψ=1,φψ=5. \varphi+\psi=1, \qquad \varphi\psi=-1, \qquad \varphi-\psi=\sqrt5.

它们都是方程

r2r1=0 r^2-r-1=0

的根,所以都满足

r2=r+1. r^2=r+1.

这意味着

φn+2=φn+1+φn,ψn+2=ψn+1+ψn. \varphi^{n+2}=\varphi^{n+1}+\varphi^n, \qquad \psi^{n+2}=\psi^{n+1}+\psi^n.

还有一个很重要的事实:

ψ<1. |\psi|<1.

因此 ψn\psi^n 会随着 nn 增大迅速趋近于 00

比如:

ψ10.618,ψ20.382,ψ50.090,ψ100.008. \psi^1\approx-0.618, \quad \psi^2\approx0.382, \quad \psi^5\approx-0.090, \quad \psi^{10}\approx0.008.

二项式展开中的抵消

φn\varphi^nψn\psi^n 展开。先看

φn=(1+52)n=(1+5)n2n. \varphi^n= \left(\frac{1+\sqrt5}{2}\right)^n = \frac{(1+\sqrt5)^n}{2^n}.

\frac{(1+\sqrt5)^n}{2^n}.

由二项式定理, 由二项式定理,

由二项式定理,

(1+5)n=k=0n(nk)(5)k. (1+\sqrt5)^n = \sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.

\sum_{k=0}^{n}\binom{n}{k}(\sqrt5)^k.

kk 是偶数时,(5)k(\sqrt5)^k 是整数;当 kk 是奇数时,(5)k(\sqrt5)^k5\sqrt5 的整数倍。

因此可以把展开式写成

(1+5)n=An+Bn5, (1+\sqrt5)^n=A_n+B_n\sqrt5,

其中 AnA_nBnB_n 都是整数。

同理,

(15)n=AnBn5. (1-\sqrt5)^n=A_n-B_n\sqrt5.

两式相减,得到

(1+5)n(15)n=2Bn5. (1+\sqrt5)^n-(1-\sqrt5)^n=2B_n\sqrt5.

于是

φnψn=(1+5)n(15)n2n=2Bn52n. \varphi^n-\psi^n = \frac{(1+\sqrt5)^n-(1-\sqrt5)^n}{2^n} = \frac{2B_n\sqrt5}{2^n}.

\frac{(1+\sqrt5)^n-(1-\sqrt5)^n}{2^n}

\frac{2B_n\sqrt5}{2^n}.

代入Binet公式: 代入 Binet 公式:

代入 Binet 公式:

Fn=φnψn5=2Bn2n. F_n = \frac{\varphi^n-\psi^n}{\sqrt5} = \frac{2B_n}{2^n}.

\frac{\varphi^n-\psi^n}{\sqrt5}

\frac{2B_n}{2^n}.

这个推导说明了为什么 5\sqrt5 会消失:两边共轭项相减后,剩下的部分都带有一个 5\sqrt5,正好被分母除掉。

不过这里还留下一个问题:2Bn/2n2B_n/2^n 看起来只是有理数,为什么一定是整数?最直接的解释是:Binet 公式右侧满足同一个斐波那契递推,而且初值也是 0011。递推定义已经唯一确定了整个数列,所以它给出的每一项就是 FnF_n,必为整数。

n=5n=5 看一次

展开:

(1+5)5=1+55+105+1055+525+255. (1+\sqrt5)^5 =1+5\sqrt5+10\cdot5+10\cdot5\sqrt5+5\cdot25+25\sqrt5.

整理得

(1+5)5=176+805. (1+\sqrt5)^5=176+80\sqrt5.

同理,

(15)5=176805. (1-\sqrt5)^5=176-80\sqrt5.

相减:

(1+5)5(15)5=1605. (1+\sqrt5)^5-(1-\sqrt5)^5=160\sqrt5.

所以

F5=15160525=16032=5. F_5 =\frac{1}{\sqrt5}\cdot\frac{160\sqrt5}{2^5} =\frac{160}{32}=5.

5\sqrt5 在中间出现,但最后消失了。

FnF_n 是最近的整数

Binet 公式还给出一个很有用的近似:

Fn=φn5ψn5. F_n=\frac{\varphi^n}{\sqrt5}-\frac{\psi^n}{\sqrt5}.

因为 ψ<1|\psi|<1,当 n1n\ge1 时,

ψn5<15<12. \left|\frac{\psi^n}{\sqrt5}\right|<\frac1{\sqrt5}<\frac12.

实际上这个误差还会随着 nn 增大迅速变小。因此 FnF_n 是离

φn5 \frac{\varphi^n}{\sqrt5}

最近的整数。

比如:

φ10555.003,F10=55. \frac{\varphi^{10}}{\sqrt5}\approx55.003, \qquad F_{10}=55.

φ2056765.00003,F20=6765. \frac{\varphi^{20}}{\sqrt5}\approx6765.00003, \qquad F_{20}=6765.

矩阵对角化再看一次

第 8 章说过,斐波那契矩阵是

M=(1110). M= \begin{pmatrix} 1&1\\ 1&0 \end{pmatrix}.

它有两个特征值 φ\varphiψ\psi。对应的特征向量可以取

(φ1),(ψ1). \begin{pmatrix}\varphi\\1\end{pmatrix}, \qquad \begin{pmatrix}\psi\\1\end{pmatrix}.

于是令

P=(φψ11),D=(φ00ψ). P= \begin{pmatrix} \varphi&\psi\\ 1&1 \end{pmatrix}, \qquad D= \begin{pmatrix} \varphi&0\\ 0&\psi \end{pmatrix}.

M=PDP1. M=PDP^{-1}.

矩阵 PP 的逆矩阵是

P1=1φψ(1ψ1φ)=15(1ψ1φ). P^{-1} = \frac1{\varphi-\psi} \begin{pmatrix} 1&-\psi\\ -1&\varphi \end{pmatrix} = \frac1{\sqrt5} \begin{pmatrix} 1&-\psi\\ -1&\varphi \end{pmatrix}.

\frac1{\varphi-\psi}
\begin{pmatrix}
1&-\psi\
-1&\varphi
\end{pmatrix}

\frac1{\sqrt5}
\begin{pmatrix}
1&-\psi\
-1&\varphi
\end{pmatrix}.

从初始向量 从初始向量

从初始向量

(10) \begin{pmatrix}1\\0\end{pmatrix}

出发,有

(Fn+1Fn)=Mn(10)=PDnP1(10). \begin{pmatrix} F_{n+1}\\ F_n \end{pmatrix} =M^n \begin{pmatrix}1\\0\end{pmatrix} =PD^nP^{-1} \begin{pmatrix}1\\0\end{pmatrix}.

先算

P1(10)=15(11). P^{-1} \begin{pmatrix}1\\0\end{pmatrix} = \frac1{\sqrt5} \begin{pmatrix}1\\-1\end{pmatrix}.

\frac1{\sqrt5}
\begin{pmatrix}1\-1\end{pmatrix}.

再乘 DnD^n

Dn15(11)=15(φnψn). D^n \frac1{\sqrt5} \begin{pmatrix}1\\-1\end{pmatrix} = \frac1{\sqrt5} \begin{pmatrix}\varphi^n\\-\psi^n\end{pmatrix}.

\frac1{\sqrt5}
\begin{pmatrix}\varphi^n\-\psi^n\end{pmatrix}.

最后乘 PP

P15(φnψn)=15(φn+1ψn+1φnψn). P\cdot \frac1{\sqrt5} \begin{pmatrix}\varphi^n\\-\psi^n\end{pmatrix} = \frac1{\sqrt5} \begin{pmatrix} \varphi^{n+1}-\psi^{n+1}\\ \varphi^n-\psi^n \end{pmatrix}.

\frac1{\sqrt5}
\begin{pmatrix}
\varphi^{n+1}-\psi^{n+1}\
\varphi^n-\psi^n
\end{pmatrix}.

第二项正是 第二项正是

第二项正是

Fn=φnψn5. F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.

所以 Binet 公式也可以看成矩阵对角化的结果。反复乘同一个矩阵,在特征向量坐标里变成分别乘 φ\varphiψ\psi

为什么无理数会给出整数

现在可以把这件事说清楚。

第一,5\sqrt5 进入公式,是因为特征方程 r2r1=0r^2-r-1=0 的根包含 5\sqrt5

第二,5\sqrt5 在结果中消失,是因为 φ\varphiψ\psi 是一对共轭根。一个含 +5+\sqrt5,另一个含 5-\sqrt5。Binet 公式取两者的差,刚好留下一个可被分母消去的 5\sqrt5

第三,最后得到整数,是因为这个表达式满足斐波那契递推,且初值就是 F0=0,F1=1F_0=0,F_1=1。递推把整数性一路传下去。

本章小结

5\sqrt5 不是错误,也不是奇迹。它是代数运算中的中间语言。用它可以把递推拆成两个独立的指数项:

φnψn. \varphi^n \quad\text{和}\quad \psi^n.

两项相减时,无理部分按共轭关系抵消,结果回到整数。下一章我们换一种更具体的语言:用铺砖和走楼梯解释同一个数列。

第 10 章 一个数列也是被数出来的

到上一章为止,我们把 FnF_n 从各种角度看过了一遍。递归、算法、矩阵、闭式公式。每一种都把 FnF_n 看成"被算出来的数"。

这一章换个角度:FnF_n 不只是被算出来的,它还是被"数"出来的。

什么意思?

考虑一段长度为 n 的窄走廊,用 1 格砖和 2 格砖把它铺满。问有多少种铺法。

这个数列是 1, 2, 3, 5, 8, 13, …(n=1,2,3,4,5,6,n = 1, 2, 3, 4, 5, 6,…)。它就是斐波那契数列错位一格。也就是说,铺 n 格走廊的方法数等于 Fn+1F_{n+1}

这件事本身在第 1 章我们已经看到过。但是这一章要做得更彻底:要让你真的相信"FnF_n 在数某个东西",而不只是"恰好等于某个数"。

先动手数

把 n = 1, 2, 3, 4 的铺法都列出来。

n=1n = 1,1 种:

1

n=2n = 2,2 种:

1+1
2

n=3n = 3,3 种:

1+1+1
1+2
2+1

n=4n = 4,5 种:

1+1+1+1
1+1+2
1+2+1
2+1+1
2+2

1, 2, 3, 5。和 F2F_2, F3F_3, F4F_4, F5F_5 完全对上。

这些数是怎么来的?是真的能数出来的。n=4n = 4的 5 种铺法,每一种都是一种具体的"砖头摆放"。数 5 个具体的摆放,得到 5。

这件事比"算 F4=3+2=5F_4=3+2=5"更直观。算的时候,5 是一个加法结果。数的时候,5 是一个计数结果。两个 5 是同一个数,但意义不同。

推递推:分最后一块

现在推一下递推。设铺 nn 格走廊的方法数为 TnT_n

考虑一条长度为 nn 的走廊的最后一块砖,也就是贴着走廊右端的那一块。它要么是 11 格砖,要么是 22 格砖。

如果最后一块是 11 格砖,那么前面剩下的是长度 n1n-1 的走廊,铺法数等于 Tn1T_{n-1}

如果最后一块是 22 格砖,那么前面剩下的是长度 n2n-2 的走廊,铺法数等于 Tn2T_{n-2}

两种情况不重不漏地覆盖了所有铺法,所以

Tn=Tn1+Tn2. T_n=T_{n-1}+T_{n-2}.

加上初值 T1=1,T2=2T_1=1,T_2=2,整个数列就定下来了:1,2,3,5,8,13,21,34,1,2,3,5,8,13,21,34,\ldots

也就是说

Tn=Fn+1. T_n=F_{n+1}.

到这里,斐波那契数列有了一个全新的解释:Fn+1F_{n+1} 是「用 11 格砖和 22 格砖铺长度 nn 走廊的方法数」。

这个解释和代数无关。它不需要递推公式,不需要矩阵,不需要 5\sqrt5。它直接告诉你 FnF_n 是某个具体物体的计数。

为什么这个解释重要

数学里有两种数:抽象的数和具体的数。

抽象的数:F10=55F_{10} = 55。这个 55 是一个抽象值,它来自一系列加法。
具体的数:长度 9 的走廊有 55 种铺法。这个 55 是一个具体计数,对应到 55 种实实在在的砖头摆放。

同一个 55,意义不同。具体的 55 可以"看",你可以在纸上画出 55 种铺法。抽象的 55 只能"算"。

这种差别在数学里反复出现。组合数学的核心工作之一,就是把抽象的数和具体的计数问题对应起来。一个数列如果能被解释为"某个东西的计数",它就有了组合意义。

斐波那契数列是组合数学最经典的样本之一。它对应很多种"计数对象",铺砖只是其中一种。

再看楼梯

第 1 章见过的:站在 n 阶楼梯下面,每次走 1 阶或 2 阶,问走到顶有多少种走法。

WnW_n 是走 nn 阶楼梯的方法数。

n=1n = 1:只能一步走完,方法数 1。
n=2n = 2:可以一步一步走,也可以一步直接跨 2 阶,方法数 2。
n=3n = 3:1+1+1、1+2、2+1,方法数 3。
n=4n = 4:1+1+1+1、1+1+2、1+2+1、2+1+1、2+2,方法数 5。

1, 2, 3, 5。Wn=Fn+1W_n=F_{n+1}

走 n 阶楼梯的方法数,和铺 n 格走廊的方法数,是同一个数。

这不是巧合。两种计数问题有同一个结构:

系统有 n 个"位置"。
每一步占据 1 个或 2 个位置。
问总共多少种"占据方式"。

楼梯和走廊只是"位置"的不同物理外衣。楼梯的位置是阶,走廊的位置是格。但计数结构是一样的。

更精确地说,铺砖和楼梯之间有一个直接的双射:把铺法 1+1+2+2+1 对应到楼梯走法"1 步, 1 步, 2 步, 2 步, 1 步"。每个 1 格砖对应 1 阶,每个 2 格砖对应 2 阶。这种对应是一一的,所以两种计数给出同一个数。

双射是组合数学里很优雅的工具。两个看似无关的集合如果能建立一一对应,它们的元素数就相等。这一点看起来平凡,但是当我们能把一个"难数"的集合对应到一个"好数"的集合时,它就变成了一个强大的工具。

一个细节:索引差一格

注意上面 T1=1=F2T_1=1=F_2T2=2=F3T_2=2=F_3,……,Tn=Fn+1T_n=F_{n+1}。索引差一格。

为什么差一格?因为我们让 T0T_0 表示「铺长度 00 的走廊的方法数」。空走廊,一种铺法,什么都不铺。所以 T0=1=F1T_0=1=F_1。从这个初值出发,Tn=Fn+1T_n=F_{n+1}

如果换一种约定,让 T0=0T_0=0,也就是「长度 00 不能铺」,那 T1=1,T2=1,T3=2T_1=1,T_2=1,T_3=2,……,Tn=FnT_n=F_n。索引就对齐了,但 T2=1T_2=1 这种结果就违反直觉,长度 22 的走廊明明有两种铺法:1+11+122

所以「差一格」是更自然的约定。第 1 章里我们说全书统一用 F0=0,F1=1F_0=0,F_1=1,组合计数问题的索引会差一格,错位时用小数字修。这里就是这种修法的具体应用。

一个稍微大一点的例子:n=6n=6

为了让你相信这件事真的成立,我们数一下 n=6n=6 的铺法。

按最后一块分类:

最后一块是 11 格砖:前面是长度 55 的走廊,有 T5=8T_5=8 种铺法。

最后一块是 22 格砖:前面是长度 44 的走廊,有 T4=5T_4=5 种铺法。

总数是

T6=T5+T4=8+5=13. T_6=T_5+T_4=8+5=13.

F7=13F_7=13。对。

这种「分最后一块」的计数方式,比直接列出 1313 种铺法更省事。它利用了递推结构,把大问题拆成两个小问题。但是注意,这里的「拆」是组合意义上的拆,不是代数意义上的拆。我们是在数:T6T_6 的所有铺法,可以分成「由 T5T_5 的铺法后接一块 11 格砖」和「由 T4T_4 的铺法后接一块 22 格砖」两类。两类不交,所以数量相加。

这是组合推理和代数推理的一个微妙差别。代数推理关心「等式成立」。组合推理关心「两个集合的元素一一对应」或「一个集合被不重不漏地分成几类」。前者是符号游戏,后者是结构对应。

计数视角带来的新可能

一旦把 FnF_n 看成"铺砖方法数",很多事情就变得可以做。

第一,可以反过来证恒等式。比如要证 F1F_1 + F2F_2 + …+Fn=Fn+21+ F_{n} = F_{n+2} - 1,可以两边都解释成"某种铺法的数量",然后证明它们数的是同一个集合。这种证明叫组合证明,它比代数证明更"看见在数什么"。第 14 章会专门做这件事。

第二,可以推广到「加权铺砖」。11 格砖有 c1c_1 种颜色可选,22 格砖有 c2c_2 种颜色可选,铺法总数满足

Tn=c1Tn1+c2Tn2. T_n=c_1T_{n-1}+c_2T_{n-2}.

c1=c2=1c_1=c_2=1 时回到斐波那契;当 c1=2,c2=1c_1=2,c_2=1 时得到 Pell 数列。这种推广让线性递推和加权铺砖对应起来。尾声会展开。

第三,可以把 FnF_n 解释成"图上某种结构的数量"。第 12 章会把铺砖推广到路径图上的匹配,看同一个递推在图论里长什么样。

本章小结

斐波那契数列不只是"被算出来的数"。它还是"被数出来的数"。它是某种具体对象的计数。

具体是什么对象?任何具有"一维位置 + 每步占 1 或 2"这种结构的对象。铺砖、楼梯,都是这种结构的不同外衣。

这种组合解释给了我们一个新的视角去看斐波那契数列:

它解释了为什么 FnF_n 满足递推 Fn=Fn1+Fn2F_n=F_{n-1}+F_{n-2}。因为对应对象的计数满足"第一步占 1 或 2"的分情况。
它给出了一个工具,组合证明,可以用计数来证恒等式,而不仅仅用代数。
它揭示了斐波那契数列的"组合本质":线性、一维、每步 1 或 2。这就是斐波那契数列的组合骨架。

到这里我们已经看到 FnF_n 的几种外衣:递归程序、矩阵幂、闭式公式、铺砖计数、楼梯计数。下一章我们会再增加两种外衣:不连续子集和不含相邻 1 的二进制串。看同一个递推怎么藏在更多场景里。

第 11 章 子集与串:同一个对象的四种写法

上一章我们看了铺砖和楼梯,两种计数都给出斐波那契数。这一章再加两种:不含相邻 11 的二进制串,和不包含连续整数的子集。

四种对象,四种外衣,背后是同一种结构。这一章的重点是建立这四种外衣之间的严格双射,让你看见"它们其实是同一件事"。

先把索引对齐

固定一个整数 NN。考虑四类对象:

  1. 长度为 N+1N+1 的走廊铺法。
  2. N+1N+1 阶楼梯、每步走 1122 阶的方法。
  3. 长度为 NN 的不含相邻 11 的二进制串。
  4. 集合 {1,2,,N}\{1,2,\dots,N\} 的不含连续整数的子集。

回忆上一章的铺砖计数定义。令 TMT_M 表示用 11 格砖和 22 格砖铺满长度为 MM 的走廊的方法数,约定 T0=1T_0 = 1。于是

TM=TM1+TM2,T0=1,T1=1, T_M = T_{M-1} + T_{M-2}, \qquad T_0 = 1, T_1 = 1,

并且

TM=FM+1. T_M = F_{M+1}.

四类对象的数量都是 FN+2F_{N+2}。这样索引就对齐了。

第一类直接代入:长度 N+1N+1 走廊的铺法数是 TN+1=FN+2T_{N+1} = F_{N+2}

第二类楼梯和铺砖的对应是显然的:每步走 11 阶对应 11 格砖,每步走 22 阶对应 22 格砖。走 N+1N+1 阶楼梯的方法数等于铺 N+1N+1 格走廊的方法数。

第三、第四类需要稍微推导,下面分别处理。

不含相邻 11 的二进制串

BNB_N 表示长度为 NN 的不含相邻 11 的二进制串的个数。约定 B0=1B_0 = 1(空串合法)。

考虑一个长度 NN 的合法串的最后一位。

如果末位是 00:前 N1N-1 位可以是任何合法串。方法数 BN1B_{N-1}
如果末位是 11:倒数第二位必须是 00(否则出现相邻 11),所以末尾两位是 0101。前 N2N-2 位可以是任何合法串。方法数 BN2B_{N-2}

所以

BN=BN1+BN2,B0=1,B1=2. B_N = B_{N-1} + B_{N-2}, \qquad B_0 = 1, B_1 = 2.

初值 B0=1=F2B_0 = 1 = F_2B1=2=F3B_1 = 2 = F_3。所以 BN=FN+2B_N = F_{N+2}

不包含连续整数的子集

SNS_N 表示 {1,2,,N}\{1,2,\dots,N\} 的不含连续整数的子集的个数。约定 S0=1S_0 = 1(空集合法)。

考虑元素 NN。一个不含连续整数的子集,要么包含 NN,要么不包含。

如果包含 NN:不能包含 N1N-1,剩下从 {1,2,,N2}\{1,2,\dots,N-2\} 里选。方法数 SN2S_{N-2}
如果不包含 NN:从 {1,2,,N1}\{1,2,\dots,N-1\} 里选。方法数 SN1S_{N-1}

所以

SN=SN1+SN2,S0=1,S1=2. S_N = S_{N-1} + S_{N-2}, \qquad S_0 = 1, S_1 = 2.

初值和 BB 完全一样,所以 SN=FN+2S_N = F_{N+2}

二进制串和不连续子集的双射

这两类对象数量相等不是巧合,它们之间有一个直接的双射。

给定一个长度 NN 的二进制串 b1b2bNb_1 b_2 \cdots b_N,定义子集

S={i:bi=1,1iN}. S = \{i : b_i = 1, 1 \le i \le N\}.

也就是说,第 ii 位是 11,就把 ii 放进子集。

这个映射显然是一一对应。串里相邻 11 等价于子集里连续整数。所以"不含相邻 11 的长度 NN 二进制串"和"不含连续整数的 {1,,N}\{1,\dots,N\} 子集"是同一个集合,只是元素写法不同。

铺砖到二进制串:内部边界法

铺砖和二进制串之间的双射不那么显然。我们要用一个严格的方法:内部边界法。

考虑长度为 N+1N+1 的走廊。它有 N+1N+1 个格子,编号 11N+1N+1。相邻格子之间有 NN 条内部边界,分别是 121|2232|3,…,NN+1N|N+1

定义从铺法到二进制串 b1b2bNb_1 b_2 \cdots b_N 的映射:

bi={1,如果第 i 条内部边界被一块 2 格砖跨过;0,如果第 i 条内部边界是两块砖之间的分界。 b_i = \begin{cases} 1, & \text{如果第 } i \text{ 条内部边界被一块 } 2 \text{ 格砖跨过;}\\ 0, & \text{如果第 } i \text{ 条内部边界是两块砖之间的分界。} \end{cases}

为什么这样定义的串不含相邻 11

如果 bi=1b_i = 1,说明有一块 22 格砖盖住第 ii 格和第 i+1i+1 格。此时第 i+1i+1 条边界不可能也被另一块 22 格砖跨过,因为那会让第 i+1i+1 格被两块砖同时占用,铺法不合法。所以 bi+1b_{i+1} 必须是 00,串里不会出现相邻 11

反向映射

给定一个长度 NN 的不含相邻 11 的二进制串 b1b2bNb_1 b_2 \cdots b_N,构造铺法。

对每个 bi=1b_i = 1,在第 ii 格和第 i+1i+1 格上放一块 22 格砖。因为串里没有相邻 11,这些 22 格砖不会重叠。所有没有被 22 格砖覆盖的位置,用 11 格砖填满。

这就是反向映射。

两个映射互为逆映射。给定铺法走正向映射得到串,再走反向映射得到铺法,结果和原来一样;反过来也成立。所以这是双射。

一个具体例子

用长度 66 的走廊,即 N=5N = 5。考虑铺法:

[1][23][4][56]

也就是:第 11 格一块 11 格砖,第 2233 格一块 22 格砖,第 44 格一块 11 格砖,第 5566 格一块 22 格砖。

内部边界有 55 条:121|2232|3343|4454|5565|6

逐条判断:

  • 121|2:第 11 格和第 22 格属于不同砖块,所以 b1=0b_1 = 0
  • 232|3:第 22 格和第 33 格属于同一块 22 格砖,所以 b2=1b_2 = 1
  • 343|4:第 33 格和第 44 格属于不同砖块,所以 b3=0b_3 = 0
  • 454|5:第 44 格和第 55 格属于不同砖块,所以 b4=0b_4 = 0
  • 565|6:第 55 格和第 66 格属于同一块 22 格砖,所以 b5=1b_5 = 1

得到二进制串 0100101001

反向验证:0100101001b2=1b_2 = 1 所以放砖盖 2233 格;b5=1b_5 = 1 所以放砖盖 5566 格;剩下 1144 格各放 11 格砖。还原成原来的铺法。

再走到子集:0100101001 对应子集 {2,5}{1,2,3,4,5}\{2, 5\} \subseteq \{1,2,3,4,5\}2255 不连续,符合约束。

四种外衣的总图

把四种对象放在一起:

       长度 N+1 的走廊铺法
              ↕  (砖的长度序列)
       走 N+1 阶楼梯的方法
              ↕  (内部边界法)
       长度 N 的不含相邻 1 的二进制串
              ↕  (位置 = 1)
       {1,...,N} 的不含连续整数的子集

四种对象两两之间都能建立双射,所以它们的数量都相等,都是 FN+2F_{N+2}

这是组合数学的力量:通过"换外衣"算数。两个集合如果能建立一一对应,它们的元素数就相等。这一点看起来平凡,但是当我们能把一个"难数"的集合对应到一个"好数"的集合时,它就变成了一个强大的工具。

加权铺砖

四种计数问题都满足同一个递推 XN=XN1+XN2X_N = X_{N-1} + X_{N-2},系数是 1111。如果改系数会怎样?

考虑"加权铺砖":11 格砖有 c1c_1 种颜色可选,22 格砖有 c2c_2 种颜色可选。铺法总数 WMW_M 满足

WM=c1WM1+c2WM2,W0=1,W1=c1. W_M = c_1 W_{M-1} + c_2 W_{M-2}, \qquad W_0 = 1, W_1 = c_1.

最后一块砖要么是 11 格(前面 M1M-1 格任意铺,最后 11 格有 c1c_1 种颜色),要么是 22 格(前面 M2M-2 格任意铺,最后 22 格有 c2c_2 种颜色)。

c1=c2=1c_1 = c_2 = 1 回到普通斐波那契铺砖。c1=2,c2=1c_1 = 2, c_2 = 1 给出 Pell 数列。c1=1,c2=2c_1 = 1, c_2 = 2 给出 Jacobsthal 数列。任何二阶线性递推都可以这样解释为"加权铺砖"。

这种推广告诉我们:斐波那契数列的"组合本质"是"一维位置 + 每步占 1122",而任何"一维位置 + 每步占 1122,每步有可选颜色"的计数问题都对应一个二阶线性递推。这是尾声要展开的主题。

本章小结

四类对象(铺砖、楼梯、二进制串、不连续子集)数量都是 FN+2F_{N+2}。这不是巧合,它们之间有严格的双射。

二进制串和不连续子集的双射是显然的:第 ii 位是 11 等价于子集包含 ii

铺砖和二进制串的双射通过"内部边界法"建立:每条内部边界是否被 22 格砖跨过,对应二进制串的一位。这个双射严格、可画图、可逆。

加权铺砖告诉我们:斐波那契是 c1=c2=1c_1 = c_2 = 1 的特例,任何 c1,c2c_1, c_2 都对应一种"加权铺砖"问题。这是尾声"一般线性递推"主题的入口。

到这里 FnF_n 已经有了五种外衣:递归程序、矩阵幂、闭式公式、铺砖计数、子集与串计数。下一章我们换一种外衣:图。看同一个递推怎么藏在点和线里。

第 12 章 把递推画成图

上一章我们把斐波那契数列从"被算出来的数"换成了"被数出来的数"。铺砖、楼梯、子集、二进制串,四种外衣,同一个递推。

这一章再换一种外衣:图。

图论听起来比组合数学更深奥一点。点和边,路径,匹配,独立集,一堆术语。但是这一章我们尽量不堆术语。要看的事情其实很简单:把一维的"位置序列"画成图上的"点和线",那个递推还在那里。

先画一条最简单的图

考虑 nn 个点排成一行,相邻两个点之间用一条边连起来。这种图叫"路径图",记作 PnP_n

P_1:●
P_2:●—●
P_3:●—●—●
P_4:●—●—●—●
P_5:●—●—●—●—●

n 个点,n-1 条边。每条边连接两个相邻的点。

这个图看起来简单到不像图。但是它确实是图,一种最简单的图。后面我们会看到,它的某些计数问题给出斐波那契数。

选一些边,让它们不相邻

考虑这样一个问题:在 P_n 里选一些边,要求选出来的边之间不能共享端点。问有多少种选法。

"不共享端点"的意思是:如果选了边 (1,2),就不能再选 (2,3),因为它们共享点 2。

这种"互不相邻的边的集合",图论里叫"匹配"(matching)。我们这里把它叫"不相邻的边集"就行。

MnM_nPnP_n 里不相邻边集的数量(包括空集)。

n=2n=2P2P_2 有 1 条边。可选空集或 {边},共 2 种。
n=3n=3P3P_3 有 2 条边(边 a, b)。可选空集、{a}、{b},不能选 {a, b}(共享中间点),共 3 种。
n=4n=4P4P_4 有 3 条边(边 a, b, c)。可选 ∅, {a}, {b}, {c}, {a, c},共 5 种({a,b}, {b,c} 不行)。
n=5n=5P5P_5 有 4 条边。∅, {a}, {b}, {c}, {d}, {a,c}, {a,d}, {b,d},共 8 种。

2, 3, 5, 8。又是斐波那契。Mn=Fn+1M_n=F_{n+1}

推一下递推。考虑 P_n 的最后一条边,也就是连接点 n1n-1 和点 n 的那条边。

如果选这条边,那么不能选它前面的边(连接 n2n-2n1n-1 的那条),剩下要从 Pn2P_{n-2} 里选不相邻边集,方法数 Mn2M_{n-2}
如果不选这条边,那么所有边集都是从 Pn1P_{n-1} 里选的,方法数 Mn1M_{n-1}

所以 Mn=Mn1+Mn2M_n=M_{n-1}+M_{n-2}。加上初值 M2=2=F3M_2=2=F_3M3=3=F4M_3=3=F_4,可得 Mn=Fn+1M_n=F_{n+1}

注意这个递推的形状和铺砖一模一样。"选最后一条边"对应"选最后一块 2 格砖","不选最后一条边"对应"最后一块是 1 格砖"。两种外衣,同一个分情况。

选一些点,让它们不相邻

换个问题。在 P_n 里选一些点,要求选出来的点之间不能相邻(不能直接由一条边连接)。问有多少种选法。

InI_nPnP_n 里不相邻点集的数量(包括空集)。

n=1n = 1:∅, {点1},共 2 种。
n=2n = 2:∅, {点1}, {点2},共 3 种(不能同时选 1 和 2)。
n=3n = 3:∅, {1}, {2}, {3}, {1,3},共 5 种。
n=4n = 4:∅, {1}, {2}, {3}, {4}, {1,3}, {1,4}, {2,4},共 8 种。

2, 3, 5, 8。In=Fn+2I_n=F_{n+2}

递推:考虑最后一个点(点 n)。

如果选点 nn,那么不能选点 n1n-1,剩下从 Pn2P_{n-2} 里选不相邻点集,方法数 In2I_{n-2}
如果不选点 n,那么从 Pn1P_{n-1} 里选,方法数 In1I_{n-1}

所以 In=In1+In2I_n=I_{n-1}+I_{n-2}I1=2=F3I_1=2=F_3I2=3=F4I_2=3=F_4。所以 In=Fn+2I_n=F_{n+2}

这和"选边"问题完全平行。差别还是初值不同,"选点"的初值多了一格,因为"一个点也不选"在某些边界情况下算的方式不同。

这个递推为什么一直出现

到这一步,你应该开始有一种感觉:这个递推 Xn=Xn1+Xn2X_n=X_{n-1}+X_{n-2} 似乎是一种很基础的结构,它会出现在任何"一维位置序列上做局部选择"的计数问题里。

具体来说,下面这些计数问题,全部给出斐波那契数(差一两格索引):

  1. 铺砖:长度 nn 走廊用 1 格和 2 格砖铺满。Fn+1F_{n+1}
  2. 楼梯:nn 阶楼梯每次走 1 或 2 阶。Fn+1F_{n+1}
  3. 不连续子集:{1,,n}\{1,\ldots,n\} 不含连续整数的子集。Fn+2F_{n+2}
  4. 二进制串:长度 nn 不含相邻 11 的串。Fn+2F_{n+2}
  5. 路径图匹配:PnP_n 不相邻边集。Fn+1F_{n+1}
  6. 路径图独立集:PnP_n 不相邻点集。Fn+2F_{n+2}

六种外衣,同一种结构。这种结构可以概括为:

有一串位置。在每个位置上做一个二选一的决定。某些组合是被禁止的,具体来说,相邻两个位置上"激进"的选择不能同时出现。

满足这种结构的计数问题,递推基本都是 Xn=Xn1+Xn2X_n=X_{n-1}+X_{n-2}。"激进选择"占两个位置,"保守选择"占一个位置。最后一步是激进还是保守,决定了 Xn2X_{n-2} 还是 Xn1X_{n-1}

这就是斐波那契数列在组合数学里反复出现的真正原因。它对应最简单的一维局部选择结构。

换一种图,递推还在吗

到这里你可能会问:路径图 PnP_n 是一个特别简单的图。如果是更复杂的图,递推还在吗?

不一定。换一种图,计数问题会完全不同。

考虑一个 nn 个点的环(cycle graph, CnC_n)。环就是路径图再加上一条边,把首尾连起来。

C_3:●—●—●—●(首尾相连,三角形)
C_4:●—●—●—●—●(首尾相连,四边形)

环上"选不相邻点集"的计数,比路径图上复杂。因为现在首尾也相邻了,"选点 1"和"选点 n"不能同时发生。

设环上不相邻点集数为 InCI^C_n

考虑点 n。

如果选点 nn:不能选点 11 和点 n1n-1。剩下要在点 2,3,,n22,3,\ldots,n-2 之间选不相邻点集,但是这是一个路径图 Pn3P_{n-3},方法数 In3I_{n-3}
如果不选点 n:剩下在点 1, 2, …, n-1 之间选,但这又是一个路径图 P_{n-1},方法数 In1I_{n-1}

所以 InC=In1+In3I^C_n=I_{n-1}+I_{n-3}。这不是斐波那契递推,它是"跨两格"的递推。

具体值:

I3C=4I^C_3=4(∅, {1}, {2}, {3};不能两两同时选)
I4C=7I^C_4=7(∅, {1}, {2}, {3}, {4}, {1,3}, {2,4})
I5C=11I^C_5=11

这不是斐波那契数列。所以"斐波那契递推"不是任何图都给出。它只对应特定结构的图,路径图这种"线性、首尾不连"的结构。

但是即使递推变了,分析思路没变:分最后一步(选点 n 还是不选),把大问题拆成两个小问题。这是组合数学的通用动作。斐波那契只是这种动作在路径图上的具体结果。

一个稍微大一点的问题:星图

考虑 nn 个点的"星图"。一个中心点,n1n-1 个叶子连到中心。

n=4n=4 的星图:

  ●
  |
●—●—●
  |
  ●

(中心点连接 3 个叶子)

星图上"不相邻边集"的计数:

每条边都和中心点相连。任意两条边都共享中心点。所以"不相邻边集"只能是空集或只含一条边。

nn 个点的星图有 n1n-1 条边。所以不相邻边集数 = 11(空集)+(n1)+(n-1)(选一条边)=n=n

n=4n=4 时是 44n=5n=5 时是 55n=6n=6 时是 66。这是线性函数,不是斐波那契。

所以图的结构变了,计数结果也变了。星图给出线性,路径图给出斐波那契,环图给出更复杂的递推。图的拓扑结构决定了计数问题的难度。

图论作为"形状的语言"

这一章可以收一下了。

图论是一种"形状的语言"。它把"对象之间的关系"画成点和线,让结构变得可视。

斐波那契数列在图论里的位置很特别:它对应最简单的一维图,路径图,上的局部选择计数。这种计数有"第一步占 1 或 2"的局部结构,所以递推是 Xn=Xn1+Xn2X_n=X_{n-1}+X_{n-2}

换一种图,递推就变。但是分析方法不变,分最后一步,把大问题拆成小问题。这是组合数学的通用动作。

到这里,斐波那契数列已经有好几种外衣了:

代数外衣:Fn=Fn1+Fn2F_{n} = F_{n-1} + F_{n-2}的递推解。
算法外衣:朴素递归、记忆化、动态规划、快速倍增、矩阵快速幂。
矩阵外衣:MnM^n 的矩阵元素。
闭式外衣:(φnψn)/5(\varphi^n-\psi^n)/\sqrt5
组合外衣:铺砖、楼梯、子集、二进制串。
图论外衣:路径图上的匹配和独立集。

每一种外衣都给出同一个数列。每一种外衣都强调了斐波那契数列的某一种性质:代数强调递推,算法强调计算,矩阵强调线性变换,闭式强调增长,组合强调计数,图论强调形状。

接下来几章会换到更深的视角,整数性质和模周期。每一种视角都会给斐波那契数列涂上一层新的颜色。

但是结构还是那个结构。

第 13 章 恒等式的宇宙:代数证法

到这一章为止,我们见过了斐波那契数列的几种外衣:递归程序、矩阵幂、闭式公式、铺砖计数、图上匹配。每一种外衣都给出了同一个数列,但给出的方式不同。

这一章和下一章要看另一件事:斐波那契数之间的恒等式。

恒等式是把几个斐波那契数连起来的等式。比如

F2n=Fn(2Fn+1Fn). F_{2n}=F_n(2F_{n+1}-F_n).

这种等式在斐波那契数列里有很多条,每一条都可以用几种不同方式证明。这一章讲代数证法,下一章讲组合证法。两章用同一组恒等式,方便比较两种证法的差别。

先列五个恒等式

我们挑五个有代表性的恒等式,作为两章的样本。

恒等式 1:加和公式

i=1nFi=Fn+21. \sum_{i=1}^{n}F_i=F_{n+2}-1.

比如 n=4n=4 时,

F1+F2+F3+F4=1+1+2+3=7, F_1+F_2+F_3+F_4=1+1+2+3=7,

F61=81=7. F_6-1=8-1=7.

恒等式 2:Cassini 恒等式

Fn+1Fn1Fn2=(1)n. F_{n+1}F_{n-1}-F_n^2=(-1)^n.

比如 n=4n=4 时,

F5F3F42=529=1=(1)4. F_5F_3-F_4^2=5\cdot2-9=1=(-1)^4.

n=5n=5 时,

F6F4F52=8325=1=(1)5. F_6F_4-F_5^2=8\cdot3-25=-1=(-1)^5.

这条恒等式的右边随奇偶变化,在斐波那契恒等式里很有辨识度。

恒等式 3:翻倍公式

F2n=Fn(2Fn+1Fn). F_{2n}=F_n(2F_{n+1}-F_n).

比如 n=5n=5 时,

F10=55, F_{10}=55,

F5(2F6F5)=5(165)=55. F_5(2F_6-F_5)=5(16-5)=55.

恒等式 4:加法公式

Fn+m=Fn+1Fm+FnFm1. F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.

比如 n=3,m=4n=3,m=4 时,

F7=13, F_7=13,

F4F4+F3F3=33+22=13. F_4F_4+F_3F_3=3\cdot3+2\cdot2=13.

恒等式 5:平方和公式

i=1nFi2=FnFn+1. \sum_{i=1}^{n}F_i^2=F_nF_{n+1}.

比如 n=4n=4 时,

12+12+22+32=15, 1^2+1^2+2^2+3^2=15,

F4F5=35=15. F_4F_5=3\cdot5=15.

这五条恒等式各有特点。加和公式把一串求和化成一个斐波那契数。Cassini 把乘积和平方的差化成 ±1\pm1。翻倍公式把 F2nF_{2n} 化成 FnF_nFn+1F_{n+1} 的组合。加法公式把 Fn+mF_{n+m} 拆成关于 nnmm 的乘积。平方和公式把一串平方和化成两个相邻斐波那契数的乘积。

代数证法 1:加和公式

要证

i=1nFi=Fn+21. \sum_{i=1}^{n}F_i=F_{n+2}-1.

nn 归纳。

n=1n=1 时,

F1=1=F31. F_1=1=F_3-1.

假设对 nn 成立,即

F1+F2++Fn=Fn+21. F_1+F_2+\cdots+F_n=F_{n+2}-1.

那么

F1+F2++Fn+Fn+1=(Fn+21)+Fn+1=Fn+31. \begin{aligned} F_1+F_2+\cdots+F_n+F_{n+1} &=(F_{n+2}-1)+F_{n+1}\\ &=F_{n+3}-1. \end{aligned}

所以对 n+1n+1 也成立。归纳完成。

这个证法很干净,但它没有说明这个等式在数什么。

代数证法 2:Cassini 恒等式

要证

Fn+1Fn1Fn2=(1)n. F_{n+1}F_{n-1}-F_n^2=(-1)^n.

用归纳法。

n=1n=1 时,

F2F0F12=101=1=(1)1. F_2F_0-F_1^2=1\cdot0-1=-1=(-1)^1.

n=2n=2 时,

F3F1F22=211=1=(1)2. F_3F_1-F_2^2=2\cdot1-1=1=(-1)^2.

假设对 nn 成立。计算 n+1n+1 的左边:

Fn+2FnFn+12=(Fn+1+Fn)FnFn+12=Fn+1Fn+Fn2Fn+12=Fn+1(FnFn+1)+Fn2=Fn+1Fn1+Fn2=(Fn+1Fn1Fn2)=(1)n=(1)n+1. \begin{aligned} F_{n+2}F_n-F_{n+1}^2 &=(F_{n+1}+F_n)F_n-F_{n+1}^2\\ &=F_{n+1}F_n+F_n^2-F_{n+1}^2\\ &=F_{n+1}(F_n-F_{n+1})+F_n^2\\ &=-F_{n+1}F_{n-1}+F_n^2\\ &=-(F_{n+1}F_{n-1}-F_n^2)\\ &=-(-1)^n\\ &=(-1)^{n+1}. \end{aligned}

所以对 n+1n+1 也成立。

这个证明的关键是符号翻转。每推进一项,右边乘上一个 1-1,于是奇偶性出现了。

代数证法 3:翻倍公式

要证

F2n=Fn(2Fn+1Fn). F_{2n}=F_n(2F_{n+1}-F_n).

从加法公式出发:

Fn+m=Fn+1Fm+FnFm1. F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.

m=nm=n,得到

F2n=Fn+1Fn+FnFn1. F_{2n}=F_{n+1}F_n+F_nF_{n-1}.

提出 FnF_n

F2n=Fn(Fn+1+Fn1). F_{2n}=F_n(F_{n+1}+F_{n-1}).

又因为

Fn1=Fn+1Fn, F_{n-1}=F_{n+1}-F_n,

所以

F2n=Fn(2Fn+1Fn). F_{2n}=F_n(2F_{n+1}-F_n).

一旦有了加法公式,翻倍公式就是它的特例。

代数证法 4:加法公式

要证

Fn+m=Fn+1Fm+FnFm1. F_{n+m}=F_{n+1}F_m+F_nF_{m-1}.

固定 mm,对 nn 归纳。

n=0n=0 时,

Fm=F1Fm+F0Fm1. F_m=F_1F_m+F_0F_{m-1}.

n=1n=1 时,

Fm+1=F2Fm+F1Fm1=Fm+Fm1. F_{m+1}=F_2F_m+F_1F_{m-1}=F_m+F_{m-1}.

假设公式对 nnn1n-1 都成立。则

Fn+1+m=Fn+m+Fn+m1=(Fn+1Fm+FnFm1)+(FnFm+Fn1Fm1)=(Fn+1+Fn)Fm+(Fn+Fn1)Fm1=Fn+2Fm+Fn+1Fm1. \begin{aligned} F_{n+1+m} &=F_{n+m}+F_{n+m-1}\\ &=(F_{n+1}F_m+F_nF_{m-1})+(F_nF_m+F_{n-1}F_{m-1})\\ &=(F_{n+1}+F_n)F_m+(F_n+F_{n-1})F_{m-1}\\ &=F_{n+2}F_m+F_{n+1}F_{m-1}. \end{aligned}

这正是加法公式对 n+1n+1 的形式。归纳完成。

这里用的是二阶归纳,因为斐波那契递推本身也是二阶的。

代数证法 5:平方和公式

要证

i=1nFi2=FnFn+1. \sum_{i=1}^{n}F_i^2=F_nF_{n+1}.

nn 归纳。

n=1n=1 时,

F12=1=F1F2. F_1^2=1=F_1F_2.

假设对 nn 成立。则

F12+F22++Fn2+Fn+12=FnFn+1+Fn+12=Fn+1(Fn+Fn+1)=Fn+1Fn+2. \begin{aligned} F_1^2+F_2^2+\cdots+F_n^2+F_{n+1}^2 &=F_nF_{n+1}+F_{n+1}^2\\ &=F_{n+1}(F_n+F_{n+1})\\ &=F_{n+1}F_{n+2}. \end{aligned}

所以对 n+1n+1 也成立。

代数证法的味道

回头看五个证明,它们有共同特点:每一步都是符号操作,不依赖图像,也不依赖具体计数对象。证明完成后,我们知道等式成立,但不一定知道等式在说什么。

代数证法的优点是严谨、机械、可执行。给定一个恒等式,只要找到归纳思路,通常就能推下去。

它的局限也很清楚。Cassini 恒等式为什么差一个 ±1\pm1?翻倍公式为什么出现 2Fn+1Fn2F_{n+1}-F_n?平方和为什么会变成 FnFn+1F_nF_{n+1}?代数证法能证明等式成立,却不一定能解释这些结构从哪里来。

一个关于矩阵的注记

矩阵证法属于代数证法的一个紧凑版本。第 7 章我们看到:

Mn=(Fn+1FnFnFn1). M^n= \begin{pmatrix} F_{n+1}&F_n\\ F_n&F_{n-1} \end{pmatrix}.

于是

MnMm=Mn+m M^nM^m=M^{n+m}

给出加法公式;

det(Mn)=det(M)n \det(M^n)=\det(M)^n

给出 Cassini 恒等式;

M2n=(Mn)2 M^{2n}=(M^n)^2

给出翻倍公式。

矩阵证法比逐项归纳更紧凑。一个矩阵等式能同时推出多条恒等式。但它仍然属于符号操作。它能告诉我们等式成立,却不一定告诉我们等式在数什么。

要看等式在数什么,就要换到组合证法。

本章小结

这一章用代数方法证明了几条斐波那契恒等式。代数证法的特点是严谨、直接、可执行;它的不足是解释性不够强。

下一章会用组合证法重证其中几条适合计数解释的恒等式。加和公式、加法公式、翻倍公式、平方和公式都能解释成具体铺法的计数。Cassini 恒等式则更适合矩阵语言,这也提醒我们:不同数学事实有不同的自然语言。

第 14 章 恒等式在数什么:组合证法与它的边界

上一章我们用代数证法证了五条斐波那契恒等式。每条都成立,但每条都看不出"在数什么"。

这一章换一种证法。挑出适合计数解释的恒等式,找一个具体的计数对象,让等式两边用两种方式数同一批对象。这种证法叫组合证法。

但是有一条恒等式我们不在这一章硬证。Cassini 恒等式的自然语言是矩阵行列式,强行写成不完整的组合证明反而会削弱可信度。本章会承认这一点。

组合证法要做什么

组合证法的核心动作是:找一个集合 SS,让等式两边用两种方式数 SS 的元素。

左边把 SS 按"某种特征"分类,得到一个表达式。
右边把 SS 按"另一种特征"分类,得到另一个表达式。
两种分类数的是同一个集合,所以两个表达式相等。

这听起来抽象,下面用铺砖做具体的演示。我们的计数对象是走廊铺法。令 TMT_M 表示用 11 格砖和 22 格砖铺满长度 MM 走廊的方法数,约定 T0=1T_0 = 1。我们已经知道

TM=FM+1. T_M = F_{M+1}.

证明一:加和公式

证明

i=1nFi=Fn+21. \sum_{i=1}^{n} F_i = F_{n+2} - 1.

考虑长度 n+1n+1 的走廊铺法。它的总数是 Tn+1=Fn+2T_{n+1} = F_{n+2}

把这些铺法分成两类。

第一类:全 11 格砖的铺法。这一类只有 11 种,整条走廊都被 11 格砖铺满。

第二类:至少有一块 22 格砖的铺法。对这一类,按"最后一块 22 格砖的位置"分类。

设最后一块 22 格砖占据第 kk 格和第 k+1k+1 格,其中 1kn1 \le k \le n。这块砖的右边没有别的 22 格砖,所以右边全是 11 格砖,唯一一种。这块砖的左边长度 k1k-1,可以任意铺,方法数 Tk1=FkT_{k-1} = F_k

kk11nn 加起来,第二类的总数是

k=1nFk. \sum_{k=1}^{n} F_k.

两类相加等于总数:

Fn+2=1+k=1nFk. F_{n+2}=1+\sum_{k=1}^{n}F_k.

移项得到

k=1nFk=Fn+21. \sum_{k=1}^{n} F_k = F_{n+2} - 1.

这就证明了加和公式。组合证法告诉我们:等式右边 Fn+21F_{n+2} - 1 数的是"长度 n+1n+1 走廊里至少有一块 22 格砖的铺法",左边 Fk\sum F_k 是按"最后一块 22 格砖位置"分类的总数。两边数同一个集合。

证明二:加法公式

证明

Fn+m=Fn+1Fm+FnFm1. F_{n+m} = F_{n+1} F_m + F_n F_{m-1}.

下面先假设 n2,m2n \ge 2, m \ge 2。当 n=1n = 1m=1m = 1 时,公式可直接代入验证。

考虑长度 n+m1n+m-1 的走廊铺法。它的总数是 Tn+m1=Fn+mT_{n+m-1} = F_{n+m}

观察第 nn 格。这一格的砖有三种状态。

情况一:第 nn 格是一块 11 格砖。

左边长度 n1n-1,任意铺,方法数 Tn1=FnT_{n-1} = F_n
右边长度 m1m-1,任意铺,方法数 Tm1=FmT_{m-1} = F_m

这一类总数 FnFmF_n F_m

情况二:第 n1n-1 格和第 nn 格被同一块 22 格砖覆盖。

左边长度 n2n-2,任意铺,方法数 Tn2=Fn1T_{n-2} = F_{n-1}
右边长度 m1m-1,任意铺,方法数 Tm1=FmT_{m-1} = F_m

这一类总数 Fn1FmF_{n-1} F_m

情况三:第 nn 格和第 n+1n+1 格被同一块 22 格砖覆盖。

左边长度 n1n-1,任意铺,方法数 Tn1=FnT_{n-1} = F_n
右边长度 m2m-2,任意铺,方法数 Tm2=Fm1T_{m-2} = F_{m-1}

这一类总数 FnFm1F_n F_{m-1}

三种情况不重不漏,所以

Fn+m=FnFm+Fn1Fm+FnFm1=(Fn+Fn1)Fm+FnFm1=Fn+1Fm+FnFm1. \begin{aligned} F_{n+m} &=F_nF_m+F_{n-1}F_m+F_nF_{m-1}\\ &=(F_n+F_{n-1})F_m+F_nF_{m-1}\\ &=F_{n+1}F_m+F_nF_{m-1}. \end{aligned}

最后一步用了斐波那契递推 Fn+1=Fn+Fn1F_{n+1} = F_n + F_{n-1}

这就证明了加法公式。组合证法告诉我们:等式数的是"长度 n+m1n+m-1 走廊的铺法",按"第 nn 格的砖的状态"分成三类。

证明三:翻倍公式

翻倍公式不必单独再铺一遍,可以作为加法公式的直接结果。

m=nm = n,加法公式给出

F2n=Fn+1Fn+FnFn1. F_{2n}=F_{n+1}F_n+F_nF_{n-1}.

提取公因子 FnF_n

F2n=Fn(Fn+1+Fn1). F_{2n}=F_n(F_{n+1}+F_{n-1}).

用恒等式 Fn+1+Fn1=2Fn+1FnF_{n+1} + F_{n-1} = 2 F_{n+1} - F_n(这是 Fn1=Fn+1FnF_{n-1} = F_{n+1} - F_n 的直接推论),得到

F2n=Fn(2Fn+1Fn). F_{2n}=F_n(2F_{n+1}-F_n).

这一节的要点是:组合证法已经解释了加法公式在数什么,翻倍公式只是把切点放在正中间,即 m=nm = n

从计数角度看,翻倍公式数的是"长度 2n12n-1 走廊的铺法",按"中间位置 nn 的砖的状态"分类。三类相加给出 Fn2+2FnFn1F_n^2 + 2 F_n F_{n-1},化简后就是 Fn(2Fn+1Fn)F_n (2 F_{n+1} - F_n)

证明四:平方和公式

证明

i=1nFi2=FnFn+1. \sum_{i=1}^{n} F_i^2 = F_n F_{n+1}.

这个证明需要一个新的计数对象:两条走廊的铺法对。

考虑两条走廊:上面一条长度 nn,下面一条长度 n1n-1。两条走廊左端对齐。我们数的是"上走廊的铺法 + 下走廊的铺法"这种有序对,总数是

TnTn1=Fn+1Fn. T_n \cdot T_{n-1} = F_{n+1} \cdot F_n.

这就是等式右边。

现在用另一种方式数同一批铺法对。

定义"共同边界":某个位置 jj 同时是上走廊铺法和下走廊铺法的砖块分界。j=0j = 0 一定是共同边界(两条走廊的左端点)。

取最后一个共同边界,设它在位置 jj,其中 0jn10 \le j \le n-1

左边:从位置 00 到位置 jj,两条走廊长度都是 jj,可以任意铺。左边铺法对的数量是 TjTj=Tj2=Fj+12T_j \cdot T_j = T_j^2 = F_{j+1}^2

右边:从位置 jj 到右端,两条走廊的剩余长度分别为 a=nja = n - ja1=n1ja - 1 = n - 1 - j,两者长度差 11。如果右边不再出现新的共同边界,那么两条走廊的砖块边界必须彼此错开。这个条件会强制右边铺法唯一。

aa 为奇数时,较长的走廊先放一块 11 格砖,之后全放 22 格砖;较短的走廊全放 22 格砖。

aa 为偶数时,较长的走廊全放 22 格砖;较短的走廊先放一块 11 格砖,之后全放 22 格砖。

因此,最后共同边界一旦确定,右边没有选择,左边两条长度相同的走廊可以任意铺。

所以最后共同边界在位置 jj 的铺法对数量是 Fj+12F_{j+1}^2

jj00n1n-1 加起来,覆盖所有铺法对:

FnFn+1=j=0n1Fj+12=i=1nFi2. F_nF_{n+1}=\sum_{j=0}^{n-1}F_{j+1}^2=\sum_{i=1}^{n}F_i^2.

这就证明了平方和公式。

建议读者画 n=5n = 5 的例子:上走廊长度 55,下走廊长度 44。标出最后共同边界的位置,验证右边的"交替"模式。

Cassini 恒等式为什么不在这里硬证

Cassini 恒等式是

Fn+1Fn1Fn2=(1)n. F_{n+1}F_{n-1}-F_n^2=(-1)^n.

这条恒等式涉及两个乘积的差,组合证法需要"符号反转双射",对本书当前读者来说不够自然。强行写成不完整的组合证明反而会让读者困惑。

它更自然的语言是矩阵行列式。回忆第 7 章的矩阵

M=(1110), M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix},

以及它的幂

Mn=(Fn+1FnFnFn1). M^n=\begin{pmatrix}F_{n+1}&F_n\\F_n&F_{n-1}\end{pmatrix}.

行列式满足 det(Mn)=det(M)n\det(M^n) = \det(M)^n。计算两边:

det(Mn)=Fn+1Fn1Fn2, \det(M^n) = F_{n+1} F_{n-1} - F_n^2,

det(M)n=(1)n. \det(M)^n = (-1)^n.

所以

Fn+1Fn1Fn2=(1)n. F_{n+1}F_{n-1}-F_n^2=(-1)^n.

这不是组合证法,但它给读者一个重要判断:不同恒等式有不同的自然语言。Cassini 恒等式的自然语言是矩阵行列式,而不是计数。

组合证法的分工

这一章做了什么?

加和公式、加法公式、翻倍公式、平方和公式,四条恒等式都被解释成具体的铺法计数。每条等式的两边数同一个集合,只是分类方式不同。

Cassini 恒等式不适合硬做组合证法,它的自然语言是矩阵行列式。这不丢脸,反而是一个判断:不是每一种数学事实都应该被强行翻译成同一种语言。

组合证法的价值不在于替代代数证法,而在于回答"这个等式到底在数什么"。代数证法告诉你等式成立,组合证法告诉你等式在数什么。两种证法都做完,理解才完整。

下一章我们换一种语言:生成函数。把整个数列压成一个对象,用对象之间的运算表示数列之间的运算。这是另一种"换语言"的样本。

第 15 章 生成函数:把数列压成一个对象

到上一章为止,我们处理斐波那契数列的方式大多是逐项处理:算 F5F_5,证 F2nF_{2n},推 Fnmod10F_n\bmod10 的周期。每次都在处理一个值、一对值,或者一个具体恒等式。

这一章换一种思路:能不能把整个数列

F0,F1,F2,F3, F_0,F_1,F_2,F_3,\ldots

当成一个对象来处理?

答案是:可以。工具叫生成函数。

把数列编码成幂级数

考虑无穷级数

G(x)=F0+F1x+F2x2+F3x3+. G(x)=F_0+F_1x+F_2x^2+F_3x^3+\cdots.

把斐波那契数列的每一项当作系数,就得到一个关于 xx 的幂级数。这个 G(x)G(x) 就是斐波那契数列的生成函数。

这里暂时不讨论级数是否收敛。我们把它当作形式幂级数:xx 只是一个占位符,两个生成函数相等,意思是每一项的系数都相等。

生成函数有用,是因为数列的递推关系会变成生成函数的代数关系。数列之间的运算,也会对应到生成函数之间的运算。

推斐波那契的生成函数

斐波那契数列满足

F0=0,F1=1,Fn=Fn1+Fn2(n2). F_0=0, \qquad F_1=1, \qquad F_n=F_{n-1}+F_{n-2}\quad(n\ge2).

它的生成函数是

G(x)=F0+F1x+F2x2+F3x3+. G(x)=F_0+F_1x+F_2x^2+F_3x^3+\cdots.

因为 F0=0,F1=1F_0=0,F_1=1,所以

G(x)=x+x2+2x3+3x4+5x5+. G(x)=x+x^2+2x^3+3x^4+5x^5+\cdots.

现在计算 G(x)xG(x)-x

G(x)x=F2x2+F3x3+F4x4+=n2Fnxn=n2(Fn1+Fn2)xn=n2Fn1xn+n2Fn2xn=xm1Fmxm+x2k0Fkxk=xG(x)+x2G(x). \begin{aligned} G(x)-x &=F_2x^2+F_3x^3+F_4x^4+\cdots\\ &=\sum_{n\ge2}F_nx^n\\ &=\sum_{n\ge2}(F_{n-1}+F_{n-2})x^n\\ &=\sum_{n\ge2}F_{n-1}x^n+ \sum_{n\ge2}F_{n-2}x^n\\ &=x\sum_{m\ge1}F_mx^m+x^2\sum_{k\ge0}F_kx^k\\ &=xG(x)+x^2G(x). \end{aligned}

因此

G(x)x=xG(x)+x2G(x). G(x)-x=xG(x)+x^2G(x).

整理得到

G(x)(1xx2)=x. G(x)(1-x-x^2)=x.

所以

G(x)=x1xx2. G(x)=\frac{x}{1-x-x^2}.

一个无穷数列,被压缩成了一个有理分式。

用生成函数推出闭式公式

生成函数还能直接推出 Binet 公式。

先把分母因式分解:

1xx2=(1φx)(1ψx), 1-x-x^2=(1-\varphi x)(1-\psi x),

其中

φ=1+52,ψ=152. \varphi=\frac{1+\sqrt5}{2}, \qquad \psi=\frac{1-\sqrt5}{2}.

于是

G(x)=x(1φx)(1ψx). G(x)=\frac{x}{(1-\varphi x)(1-\psi x)}.

做部分分式分解:

G(x)=15(11φx11ψx). G(x)= \frac1{\sqrt5} \left( \frac1{1-\varphi x} - \frac1{1-\psi x} \right).

\frac1{1-\psi x}
\right).

验证也很直接:右边通分后,分子是 验证也很直接:右边通分后,分子是

验证也很直接:右边通分后,分子是

(1ψx)(1φx)=(φψ)x=5x. (1-\psi x)-(1-\varphi x)=(\varphi-\psi)x=\sqrt5x.

所以右边确实等于

x(1φx)(1ψx). \frac{x}{(1-\varphi x)(1-\psi x)}.

再用几何级数展开:

11ax=n0anxn. \frac1{1-ax}=\sum_{n\ge0}a^nx^n.

于是

G(x)=15(n0φnxnn0ψnxn)=n0φnψn5xn. \begin{aligned} G(x) &=\frac1{\sqrt5} \left( \sum_{n\ge0}\varphi^nx^n- \sum_{n\ge0}\psi^nx^n \right)\\ &=\sum_{n\ge0} \frac{\varphi^n-\psi^n}{\sqrt5}x^n. \end{aligned}

另一方面,

G(x)=n0Fnxn. G(x)=\sum_{n\ge0}F_nx^n.

对比系数,得到

Fn=φnψn5. F_n=\frac{\varphi^n-\psi^n}{\sqrt5}.

这正是 Binet 公式。

这个过程说明:闭式公式、特征方程、生成函数分母因式分解,是同一件事的三种说法。

用生成函数推加和公式

生成函数还可以处理部分和。比如要证

F1+F2++Fn=Fn+21. F_1+F_2+\cdots+F_n=F_{n+2}-1.

部分和对应生成函数除以 1x1-x。因为

11x=1+x+x2+x3+, \frac1{1-x}=1+x+x^2+x^3+\cdots,

所以

G(x)1x=(n0Fnxn)(n0xn). \frac{G(x)}{1-x} = \left(\sum_{n\ge0}F_nx^n\right) \left(\sum_{n\ge0}x^n\right).

\left(\sum_{n\ge0}F_nx^n\right)
\left(\sum_{n\ge0}x^n\right).

乘开后,xnx^n 的系数是

F0+F1++Fn. F_0+F_1+\cdots+F_n.

这就是说,G(x)/(1x)G(x)/(1-x) 编码了斐波那契数列的部分和。

代入 G(x)G(x)

G(x)1x=x(1xx2)(1x). \frac{G(x)}{1-x} = \frac{x}{(1-x-x^2)(1-x)}.

\frac{x}{(1-x-x^2)(1-x)}.

这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则: 这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则:

这条式子可以继续部分分式分解,得到部分和公式。即使不把分解细节写完,也可以看到一个重要规则:

部分和除以 1x. \text{部分和}\quad\Longleftrightarrow\quad\text{除以 }1-x.

生成函数把求和操作变成了代数操作。

生成函数作为压缩包

生成函数的本质,是把整个数列压成一个对象。

斐波那契数列有无穷多项:

F0,F1,F2, F_0,F_1,F_2,\ldots

生成函数把它压成

G(x)=x1xx2. G(x)=\frac{x}{1-x-x^2}.

这个分式里包含了整个数列的信息。

为什么这有用?因为对一个分式做代数运算,常常比对无穷多个系数逐项处理容易。

更一般地:

  • 数列加法对应生成函数加法;
  • 数列移位对应生成函数乘 xx
  • 数列卷积对应生成函数乘法;
  • 数列部分和对应生成函数除以 1x1-x
  • 数列差分对应生成函数乘以 1x1-x

这些对应让「证明数列恒等式」变成「做生成函数代数」。

生成函数和线性递推

斐波那契数列的生成函数分母是

1xx2. 1-x-x^2.

它对应递推

Fn=Fn1+Fn2. F_n=F_{n-1}+F_{n-2}.

如果换成递推

an=3an12an2, a_n=3a_{n-1}-2a_{n-2},

生成函数的分母就会出现

13x+2x2. 1-3x+2x^2.

这不是巧合。对线性递推来说,生成函数通常是有理函数。分母记录递推关系,分子记录初值修正。

可以粗略记成:

线性递推有理生成函数. \text{线性递推}\quad\Longleftrightarrow\quad\text{有理生成函数}.

这是生成函数处理线性递推的核心原因。

生成函数还能做什么

生成函数不只用来推闭式公式。它在组合数学里有很广的用途。

第一,处理带参数的计数。比如用 11 格砖和 22 格砖铺走廊,11 格砖有 aa 种颜色,22 格砖有 bb 种颜色,铺法生成函数会变成

11axbx2. \frac1{1-ax-bx^2}.

第二,处理多个数列之间的关系。斐波那契数列和 Lucas 数列有相同的递推,因此它们的生成函数分母相同,只是分子不同:

GF(x)=x1xx2,GL(x)=2x1xx2. G_F(x)=\frac{x}{1-x-x^2}, \qquad G_L(x)=\frac{2-x}{1-x-x^2}.

第三,处理卷积递推。比如 Catalan 数满足

C(x)=1+xC(x)2. C(x)=1+xC(x)^2.

这个方程可以解出

C(x)=114x2x. C(x)=\frac{1-\sqrt{1-4x}}{2x}.

斐波那契只是生成函数的一个简单样本。学会这个样本后,许多计数问题都可以用类似方法处理。

生成函数不是万能的

最后看一个反例:Cassini 恒等式

Fn+1Fn1Fn2=(1)n. F_{n+1}F_{n-1}-F_n^2=(-1)^n.

它涉及乘积 Fn+1Fn1F_{n+1}F_{n-1} 和平方 Fn2F_n^2。生成函数擅长处理加法、移位、卷积,但不太擅长直接处理这种逐项乘积。

Cassini 用矩阵行列式最干净:

det(Mn)=det(M)n. \det(M^n)=\det(M)^n.

这一步直接给出

Fn+1Fn1Fn2=(1)n. F_{n+1}F_{n-1}-F_n^2=(-1)^n.

这个例子说明:每条恒等式都有自己的自然语言。加和公式适合生成函数或组合计数,Cassini 适合矩阵行列式,翻倍公式适合矩阵平方或加法公式。

本章小结

生成函数把整个数列压成一个对象。斐波那契数列的生成函数是

G(x)=x1xx2. G(x)=\frac{x}{1-x-x^2}.

它让闭式公式的推导变成机械过程:递推变成生成函数,分母因式分解,部分分式展开,再对比系数。

生成函数也把数列运算变成代数运算。部分和对应除以 1x1-x,移位对应乘 xx,卷积对应乘法。

但生成函数不是万能的。它对线性结构很强,对 Cassini 这类逐项乘积不直接。不同恒等式有不同的自然语言。下一章开始,我们把 FnF_n 当作整数看,研究它的整除性和模周期。

第 16 章 整除性:下标带算术信息

到这一章为止,我们把斐波那契数列从各种角度看过了一遍:递归、算法、矩阵、闭式、组合、图论、恒等式、生成函数。每一种视角都把同一组数翻译成一种语言。

这一章换一个新视角:把 FnF_n 当作整数看,问它有什么"整数性质"。

具体来说:FnF_n 之间能整除吗?什么时候 FmF_{m} 能整除 FnF_n?两个斐波那契数的最大公约数是什么?

这些问题看起来像数论趣闻。但是它们其实揭示了一件事:FnF_n 的下标 n 不只是一个标号,它本身带着算术信息。n 的整除性质会反映到 FnF_n 的整除性质上。

先列出来观察

把前若干个 FnF_n 列出来:

nn FnF_n nn FnF_n nn FnF_n
1 1 11 89 21 10946
2 1 12 144 22 17711
3 2 13 233 23 28657
4 3 14 377 24 46368
5 5 15 610 25 75025
6 8 16 987 26 121393
7 13 17 1597 27 196418
8 21 18 2584 28 317811
9 34 19 4181 29 514229
10 55 20 6765 30 832040

哪些是偶数?F3=2,F6=8,F9=34,F12=144,F15=610,F18=2584,F21=10946,F24=46368,F27=196418,F30=832040F_{3} = 2, F_{6} = 8, F_{9} = 34, F_{12} = 144, F_{15} = 610, F_{18} = 2584, F_{21} = 10946, F_{24} = 46368, F_{27} = 196418, F_{30} = 832040

下标:3, 6, 9, 12, 15, 18, 21, 24, 27, 30。每 3 项出现一个偶数。FnF_n 是偶数当且仅当 n 是 3 的倍数。

哪些能被 3 整除?F4=3,F8=21,F12=144,F16=987,F20=6765,F24=46368,F28=317811F_{4} = 3, F_{8} = 21, F_{12} = 144, F_{16} = 987, F_{20} = 6765, F_{24} = 46368, F_{28} = 317811

下标:4, 8, 12, 16, 20, 24, 28。每 4 项。FnF_n 被 3 整除当且仅当 n 是 4 的倍数。

哪些能被 5 整除?F5=5,F10=55,F15=610,F20=6765,F25=75025,F30=832040F_{5} = 5, F_{10} = 55, F_{15} = 610, F_{20} = 6765, F_{25} = 75025, F_{30} = 832040

下标:5, 10, 15, 20, 25, 30。每 5 项。FnF_n 被 5 整除当且仅当 n 是 5 的倍数。

规律浮现:FnF_nFkF_{k} 整除似乎和 n 被 k 整除有关。具体来说:

m3m \ge 3 时,FmF_m 整除 FnF_n 当且仅当 mm 整除 nn。若 m=1,2m = 1, 2,因 Fm=1F_m = 1,总是整除。

这是一个非常干净的结论。它说斐波那契数列的"整除结构"和"下标的整除结构"是同构的。

推一下:FmF_{m} 整除 FkmF_{km}

我们先证明一个弱一点的版本:如果 m 整除 n 且 m \geq 3,那么 FmF_{m} 整除 FnF_n

n 是 m 的倍数,写成 n = km。我们要证 FmF_{m} | FkmF_{km}

回忆第 5 章的加法公式:

Fn+m=Fn+1Fm+FnFm1F_{n+m} = F_{n+1} F_{m} + F_{n} F_{m-1}

变形一下,让 n=(k1)mn = (k-1)m

Fkm=F(k1)m+m=F(k1)m+1Fm+F(k1)mFm1. F_{km} = F_{(k-1)m + m} = F_{(k-1)m + 1} \cdot F_m + F_{(k-1)m} \cdot F_{m-1}.

所以 Fkm=FmF(k1)m+1+F(k1)mFm1F_{km} = F_m \cdot F_{(k-1)m + 1} + F_{(k-1)m} \cdot F_{m-1}

如果 FmF_m 整除 F(k1)mF_{(k-1)m},那么 FmF_m 整除上面这个等式的右边(两项都被 FmF_m 整除),所以 FmF_m 整除 FkmF_{km}

这是对 kk 的归纳。基础 k=1k = 1FmF_m 整除 FmF_m,显然。归纳步从 k1k-1 推到 kk,刚才那段就是。

所以 FmFkmF_m \mid F_{km},即 mnm \mid n 蕴含 FmFnF_m \mid F_n

反过来呢?如果 FmFnF_m \mid F_n,是不是 mnm \mid n

也是。这个方向的证明稍微绕一点,需要用到一个更强的结论,下一节的 gcd 公式。

最大公约数定理

最强的整除性结论是:

gcd(Fm,Fn)=Fgcd(m,n). \gcd(F_m, F_n) = F_{\gcd(m, n)}.

也就是说,两个斐波那契数的最大公约数,等于它们下标最大公约数对应的斐波那契数。

这个定理一上来可能让人愣一下。它说斐波那契数列的"算术结构"完全镜像了下标的算术结构。下标的 gcd 决定斐波那契数的 gcd。

举几个例子验证:

  • gcd(F6,F9)=gcd(8,34)=2=F3=Fgcd(6,9)\gcd(F_6, F_9) = \gcd(8, 34) = 2 = F_3 = F_{\gcd(6, 9)}。对。
  • gcd(F8,F12)=gcd(21,144)=3=F4=Fgcd(8,12)\gcd(F_8, F_{12}) = \gcd(21, 144) = 3 = F_4 = F_{\gcd(8, 12)}。对。
  • gcd(F10,F15)=gcd(55,610)=5=F5=Fgcd(10,15)\gcd(F_{10}, F_{15}) = \gcd(55, 610) = 5 = F_5 = F_{\gcd(10, 15)}。对。
  • gcd(F12,F18)=gcd(144,2584)=2=F6=Fgcd(12,18)\gcd(F_{12}, F_{18}) = \gcd(144, 2584) = 2 = F_6 = F_{\gcd(12, 18)}。对。

公式稳。

推一下 gcd 公式

证明 gcd(Fm,Fn)=Fgcd(m,n)\gcd(F_m, F_n) = F_{\gcd(m, n)} 需要用到一个关键引理:

gcd(Fm,Fm+n)=gcd(Fm,Fn). \gcd(F_m, F_{m+n}) = \gcd(F_m, F_n).

为什么呢?加法公式给出 Fm+n=Fm+1Fn+FmFn1F_{m+n} = F_{m+1} F_n + F_m F_{n-1}。所以:

gcd(Fm,Fm+n)=gcd(Fm,Fm+1Fn+FmFn1). \gcd(F_m, F_{m+n}) = \gcd(F_m, F_{m+1} F_n + F_m F_{n-1}).

任何 FmF_mFm+1F_{m+1} 的公因子都同时整除 FmF_mFm+1F_{m+1}。但 FmF_mFm+1F_{m+1} 互素(连续两项互素,因为 Fm+1=Fm+Fm1F_{m+1} = F_m + F_{m-1},所以 gcd(Fm,Fm+1)=gcd(Fm,Fm1)==gcd(F1,F2)=1\gcd(F_m, F_{m+1}) = \gcd(F_m, F_{m-1}) = \cdots = \gcd(F_1, F_2) = 1)。

那么 gcd(Fm,Fm+1Fn+FmFn1)=gcd(Fm,Fm+1Fn)\gcd(F_m, F_{m+1} F_n + F_m F_{n-1}) = \gcd(F_m, F_{m+1} F_n)。因为 FmF_m 整除 FmFn1F_m F_{n-1} 那一项,可以扔掉。

gcd(Fm,Fm+1Fn)=gcd(Fm,Fn)\gcd(F_m, F_{m+1} F_n) = \gcd(F_m, F_n),因为 FmF_mFm+1F_{m+1} 互素。

所以 gcd(Fm,Fm+n)=gcd(Fm,Fn)\gcd(F_m, F_{m+n}) = \gcd(F_m, F_n)

这个等式是关键。它说"两个斐波那契数的 gcd"满足"辗转相减"的规则:把较大的下标换成差,gcd 不变。

回忆欧几里得算法:gcd(m,n)=gcd(m,nm)\gcd(m, n) = \gcd(m, n-m)(当 n>mn > m 时)。我们的引理说 gcd(Fm,Fn)=gcd(Fm,Fnm)\gcd(F_m, F_n) = \gcd(F_m, F_{n-m})。这正好是欧几里得算法在"斐波那契下标"上的镜像。

反复应用这个等式,相当于在 (m,n)(m, n) 上跑欧几里得算法:

gcd(Fm,Fn)=gcd(Fm,Fnm)=gcd(Fm,Fn2m)==gcd(Fm,Fnmodm). \gcd(F_m, F_n) = \gcd(F_m, F_{n-m}) = \gcd(F_m, F_{n-2m}) = \cdots = \gcd(F_m, F_{n \bmod m}).

然后倒过来继续:

gcd(Fm,Fnmodm)=gcd(Fmmod(nmodm),Fnmodm)= \gcd(F_m, F_{n \bmod m}) = \gcd(F_{m \bmod (n \bmod m)}, F_{n \bmod m}) = \cdots

一直跑到两边相等,得到 gcd(Fg,Fg)=Fg\gcd(F_g, F_g) = F_g,其中 g=gcd(m,n)g = \gcd(m, n)

所以 gcd(Fm,Fn)=Fgcd(m,n)\gcd(F_m, F_n) = F_{\gcd(m, n)}

这个定理说明了什么

gcd(Fm,Fn)=Fgcd(m,n)\gcd(F_m, F_n) = F_{\gcd(m, n)} 这个定理,在数论里非常特别。

它说斐波那契数列的"算术结构"(哪些项能整除哪些项、两个项的最大公约数是多少)完全由"下标的算术结构"决定。

也就是说,下标 nn 不只是一个标号。它带着算术信息。nn 的因子结构、nn 和其他下标的 gcd,这些信息直接反映到 FnF_n 的因子结构上。

这种"下标 ↔ 值"的算术对应,是非常特殊的。大多数数列没有这种性质。比如:

平方数列 n2n^2:实际上满足 mnm2n2m \mid n \Rightarrow m^2 \mid n^2,所以它也有"下标整除蕴含值整除"的性质,但它没有斐波那契的 gcd 镜像结构,即 gcd(m2,n2)=(gcd(m,n))2\gcd(m^2, n^2) = (\gcd(m,n))^2 虽然成立,但镜像不如斐波那契那么干净。
阶乘数列 n!n!mnm \mid n 不蕴含 m!n!m! \mid n!(实际上 m!m! 整除 n!n!mnm \le n,这和下标整除无关)。
素数数列 pnp_nmnm \mid n 不蕴含 pmpnp_m \mid p_n5105 \mid 10p5=11p_5 = 11 不整除 p10=29p_{10} = 29)。

所以斐波那契数列的"下标-值算术对应"是一种特殊的、不是所有数列都有的性质。这种性质来自斐波那契数列的"线性递推 + 整数系数"结构。矩阵 MM 的整数性质让 FmF_mFnF_n 之间的算术关系镜像 mmnn 之间的关系。

一个有趣的副产品:FpF_p 和素数 pp

把整除定理用一下,可以推出一些关于素数 pp 的有趣性质。

如果 pp 是素数,那么 gcd(Fp,Fn)\gcd(F_p, F_n) 只能是 11FpF_p。具体来说,gcd(Fp,Fn)=Fgcd(p,n)=F1=1\gcd(F_p, F_n) = F_{\gcd(p, n)} = F_1 = 1(如果 pp 不整除 nn)或 FpF_p(如果 pp 整除 nn)。

所以 FpF_p 和"下标不被 pp 整除的斐波那契数"都互素。这有点像"pp 是素数     \iff pp 和所有比它小的正整数互素"在斐波那契数列里的镜像。

更深的数论性质(比如 Pisano 周期和 FpmodpF_p \bmod p 的关系)会出现在下一章。这里只是让你看到,整除定理不光是个孤立结论,它和数论里更深的结构连着。

整除性给我们的视角

到这里可以收一下。

整除性定理说:当 m3m \ge 3 时,FmFnF_m \mid F_n 当且仅当 mnm \mid ngcd(Fm,Fn)=Fgcd(m,n)\gcd(F_m, F_n) = F_{\gcd(m, n)}

这两件事给我们的视角是:斐波那契数列的"算术结构"和"下标的算术结构"同构。下标不只是一个标号,它带着算术信息。

这种视角在数学里非常珍贵。它意味着如果你想研究 FnF_n 的因子结构,你可以转而研究 nn 的因子结构,后者通常更容易。

举一个具体的例子:要知道 F100F_{100} 能被哪些斐波那契数整除,我们不需要去算 F100=354224848179261915075F_{100} = 354224848179261915075 然后做因子分解。我们只需要找出 100100 的所有因子:1,2,4,5,10,20,25,50,1001, 2, 4, 5, 10, 20, 25, 50, 100。所以 F100F_{100}F1=1F_1 = 1F2=1F_2 = 1F4=3F_4 = 3F5=5F_5 = 5F10=55F_{10} = 55F20=6765F_{20} = 6765F25=75025F_{25} = 75025F50F_{50}F100F_{100} 这些斐波那契数整除。

不需要算大数。下标的因子结构就够了。

这就是"下标带算术信息"这件事的力量。

数论作为"算术结构的研究"

最后说一下数论在做什么。

数论经常被描述为"研究整数的性质"。这个描述没错,但太宽。更精确一点说,数论研究的是"算术结构",加法、乘法、整除、模运算这些操作下,整数系统里有哪些规律。

斐波那契数列给数论提供了一个具体的舞台。它是一个由整数组成的数列,但是它的算术结构(哪些项整除哪些项、gcd 是什么)和它的下标的算术结构有紧密联系。这种联系不是显然的,需要证明。但是一旦证出来,就给出一种"通过下标算 FnF_n"的捷径。

下一章我们继续这种"算术结构"的探索,但视角更窄,只看余数。看 FnmodmF_n \bmod m 的余数有什么规律。结果会发现一个让人意想不到的事实:FnmodmF_n \bmod m 总会循环。这种循环是有限状态系统的必然结果。

第 17 章 模运算与 Pisano 周期:发现循环

这一章只看斐波那契数列的最后一位数字。

听起来好像很窄。但是这一位数字里藏着一个非常漂亮的事实:它每 60 项循环一次。

我们会把这个循环解释清楚。结论是:任何有限状态系统都会循环,斐波那契数列模 m 的余数序列是这种循环的最干净的样本。

先把循环列出来

FnF_n mod 10 列出来(也就是 FnF_n 的最后一位):

n:      0  1  2  3  4  5  6  7   8   9   10  11  12  13  14  15
F_n:    0  1  1  2  3  5  8  13  21  34  55  89  144 233 377 610
mod 10: 0  1  1  2  3  5  8  3   1   4   5   9   4   3   7   0

继续:

n:      16  17   18   19   20   21    22    23    24    25     26      27      28      29      30
F_n:    987 1597 2584 4181 6765 10946 17711 28657 46368 75025  121393  196418  317811  514229  832040
mod 10: 7   7    4    1    5    6     1     7     8     5      3       8       1       9       0

继续:

n:      31      32      33      34      35      36       37       38       39       40
F_n:    1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155
mod 10: 9       9       8       7       5       2        7        9        6        5

继续:

n:    41 42 43 44 45 46 47 48 49 50
mod 10: 1  6  7  3  0  3  3  6  9  5

继续:

n:    51 52 53 54 55 56 57 58 59 60
mod 10: 4  9  3  2  5  7  2  9  1  0

到 n = 60,mod 10 = 0。然后 n = 61 应该是 1(F61F_{61} mod 10),n=62n = 62应该是 1。事实上:

n:    60 61 62 63 64 ...
mod 10: 0  1  1  2  3 ...

从 n = 60 开始,序列回到 0, 1, 1, 2, 3, …,和从 n = 0 开始完全一样。

也就是说,FnF_n mod 10 的序列,每 60 项循环一次。

这个 60 叫"模 10 的 Pisano 周期",记 π(10)=60\pi(10)=60

先看几个小模数

60 这个数字看起来很神秘。先看几个小模数,找找规律。

模 2

n:    0  1  2  3  4  5  6  7  8  9  10 ...
mod 2: 0  1  1  0  1  1  0  1  1  0  1 ...

每 3 项循环。π(2)=3\pi(2) = 3

模 3

n:    0  1  2  3  4  5  6  7  8  9  10 11 12 ...
mod 3: 0  1  1  2  0  2  2  1  0  1  1  2  0 ...

每 8 项循环。π(3)=8\pi(3) = 8

模 4

n:    0  1  2  3  4  5  6  7  8  9  10 11 12 ...
mod 4: 0  1  1  2  3  1  0  1  1  2  3  1  0 ...

每 6 项循环。π(4)=6\pi(4) = 6

模 5

n:    0  1  2  3  4  5  6  7  8  9  10 ...
mod 5: 0  1  1  2  3  0  3  3  1  4  0 ...

每 20 项循环。π(5)=20\pi(5) = 20

把前若干个 Pisano 周期列出来:

m:    1  2  3  4  5  6   7   8   9   10  11  12
π(m): 1  3  8  6  20 24  16  12  24  60  10  24

观察:

π(2)=3π(4)=6=2π(2)π(8)=12=2π(4)π(16)=24=2π(8) \pi(2) = 3 \pi(4) = 6 = 2 \cdot \pi(2) \pi(8) = 12 = 2 \cdot \pi(4) \pi(16) = 24 = 2 \cdot \pi(8)

似乎

π(2k)=32k1. \pi(2^k)=3\cdot2^{k-1}.

π(5)=20π(25)=100=5π(5) \pi(5) = 20 \pi(25) = 100 = 5 \cdot \pi(5)

似乎

π(5k)=45k. \pi(5^k)=4\cdot5^k.

π(10)=60=lcm(π(2),π(5))=lcm(3,20)=60\pi(10) = 60 = \operatorname{lcm}(\pi(2), \pi(5)) = \operatorname{lcm}(3, 20) = 60。也就是说,模 10 的周期是模 2 周期和模 5 周期的最小公倍数。

这些观察里,最重要的是最后一条:π(m1m2)=lcm(π(m1),π(m2))\pi(m_1m_2)=\operatorname{lcm}(\pi(m_1),\pi(m_2)),当 m1m_1m2m_2 互素时。这件事下一章会用,叫"中国剩余定理拆分"。

为什么必然循环

现在到关键问题:为什么 FnF_n mod m 总是循环?

答案是:因为它是有限状态系统。

考虑"状态对" (FnF_n mod m, Fn+1F_{n+1} mod m)。这个状态对的取值范围是 {0, 1, …, m-1} ×\times {0, 1, …, m-1},总共 m2m^2 种可能。

按斐波那契递推,下一对状态是 (Fn+1F_{n+1} mod m, Fn+2F_{n+2} mod m) = (Fn+1F_{n+1} mod m, (FnF_n + Fn+1F_{n+1}) mod m)。这个变换完全由当前状态决定,不需要其他信息。

所以这是一个"状态确定下一状态"的有限状态机:m2m^2 个状态,每个状态有一个唯一的"下一个状态"。

从初始状态 (F0,F1)=(0,1)(F_0,F_1)=(0,1) 出发,按这个机器走 m2m^2 + 1 步,必然会重复某个状态。鸽巢原理:m2m^2 + 1 个状态里至少有两个相同。

一旦某个状态重复,从它出发的后续轨迹也必然相同,因为状态转移是确定的。所以从第一次重复开始,整个序列进入循环。

也就是说,FnF_n mod m 的序列,必然在 m2m^2 + 1 步内进入循环。循环长度 \leq m2m^2

这就是 Pisano 周期的本质:有限状态系统的必然周期性。

鸽巢原理的具体应用

把这件事再讲细一点。

鸽巢原理说:把 n+1 个物体放进 n 个抽屉,至少有一个抽屉里有至少两个物体。

这里"物体"是状态对,"抽屉"是 m2m^2 种可能的状态对。从 (0, 1) 出发,走 m2m^2 + 1 步产生 m2m^2 + 1 个状态对。它们都属于 m2m^2 种可能,所以至少有两个相同。

设这两个状态对分别出现在第 i 步和第 j 步(i < j)。那么 (FiF_{i}, Fi+1F_{i+1}) \equiv (FjF_{j}, Fj+1F_{j+1}) mod m。

从这两个相同状态出发,后续轨迹必然相同。所以对任何 k \geq 0,(Fi+kF_{i+k}, Fi+k+1F_{i+k+1}) \equiv (Fj+kF_{j+k}, Fj+k+1F_{j+k+1}) mod m。

也就是说,从 i 开始,序列以 j - i 为周期循环。

注意 j - i \leq m2m^2。所以循环长度不超过 m2m^2。这是 Pisano 周期的上界。

实际周期可能远小于 m2m^2。比如 m = 10,m2=100m^2=100,但实际 π(10)=60\pi(10)=60m=100m = 100m2=10000m^2=10000,实际 π(100)=300\pi(100)=300m=1000m = 1000m2=106m^2=10^6,实际 π(1000)=1500\pi(1000)=1500

实际周期比上界小很多。具体周期是多少取决于 m 的代数结构,这一点下一章会展开。

这章的核心:必然循环这件事本身不需要深度数论

到这里可以下个判断。

FnF_n mod m 的余数序列必然循环,循环长度 π(m)\pi(m) \leq m2m^2。这是有限状态系统的必然结果。

这个结论干净利落,不依赖任何深度数论。它的本质是:

FnF_n 的递推只需要前两项。
模 m 下,前两项的取值范围有限(m2m^2 种)。
有限状态 + 确定转移 = 必然循环。

这就是斐波那契数列个位数循环的真正原因。鸽巢原理就够了。

具体周期 π(m)\pi(m) 是多少,是一个深得多的问题,涉及模素数的特征根、二次剩余、二次互反律。但是"必然循环"这件事本身,只需要鸽巢原理就够了。

一个直观画面

最后留一个画面。

把"状态对"想象成棋盘上的格子。棋盘是 m ×\times m 的,每个格子代表一个 (FnF_n, Fn+1F_{n+1}) mod m 的状态对。从 (0, 1) 这个格子出发,按递推走,每次跳到一个新格子。

棋盘只有 m2m^2 个格子。走 m2m^2 + 1 步必然重复一个格子。重复之后,后面的轨迹就和第一次经过那个格子时一样。所以序列必然进入循环。

m=10m=10 时,棋盘有 10×10=10010 \times 10=100 个格子。FnF_n mod 10 的序列走过 60 个不同的格子后回到起点,形成 60 步的循环。剩下 40 个格子没被访问。

m=2m=2 时,棋盘有 2×2=42\times2=4 个格子。FnF_n mod 2 的序列走过 3 个格子后回到起点,形成 3 步循环。1 个格子没被访问。

这种"棋盘上的轨迹"画面让 Pisano 周期变得可视。循环不是抽象的代数事实,是棋盘上轨迹回到起点的几何事实。

本章小结

FnF_n mod m 的余数序列必然循环。这个事实不依赖深度数论,鸽巢原理就够了。

具体周期 π(m)\pi(m) 因 m 而异:π(2)=3,π(3)=8,π(4)=6,π(5)=20,π(10)=60\pi(2) = 3, \pi(3) = 8, \pi(4) = 6, \pi(5) = 20, \pi(10) = 60。周期上界是 m2m^2

模 10 的周期是 60。这个数字看起来神秘,但是下一章我们会看到,60=lcm(π(2),π(5))=lcm(3,20)60 = \operatorname{lcm}(\pi(2), \pi(5)) = \operatorname{lcm}(3, 20)。模 10 的周期是模 2 周期和模 5 周期的最小公倍数,由中国剩余定理保证。

鸽巢原理是这一章的全部精髓。它告诉我们:任何"状态确定下一状态"的有限系统都会循环。看见任何有限状态系统,第一反应应该是"它会循环"。

下一章我们解释为什么 π(10)=60\pi(10)=60,深入到素数情况和二次剩余。

第 18 章 Pisano 周期深入:素数与中国剩余定理

上一章我们发现,FnmodmF_n\bmod m 必然循环,而且 π(10)=60\pi(10)=60。但 6060 这个数字看上去有点突然。这一章解释它从哪儿来。

主要工具有两个:中国剩余定理,以及素数模数下的特征根分析。

中国剩余定理:把模拆开

中国剩余定理说:如果 m1m_1m2m_2 互素,那么模 m1m2m_1m_2 的余数,等价于同时知道模 m1m_1 和模 m2m_2 的余数。也就是说,对任何整数 aa,下面这对值

(amodm1,amodm2) (a\bmod m_1,\,a\bmod m_2)

唯一确定 amodm1m2a\bmod m_1m_2

把这个定理用到斐波那契数列上,模 m1m2m_1m_2 的状态

(Fn,Fn+1)modm1m2 (F_n,F_{n+1})\bmod m_1m_2

等价于同时知道

(Fn,Fn+1)modm1 (F_n,F_{n+1})\bmod m_1

(Fn,Fn+1)modm2. (F_n,F_{n+1})\bmod m_2.

如果模 m1m_1 的周期是 π(m1)\pi(m_1),模 m2m_2 的周期是 π(m2)\pi(m_2),那么模 m1m2m_1m_2 的状态要等两边同时回到起点,才会回到起点。最小的共同回归时间是最小公倍数。

因此,当 gcd(m1,m2)=1\gcd(m_1,m_2)=1 时,

π(m1m2)=lcm(π(m1),π(m2)). \pi(m_1m_2)=\operatorname{lcm}\bigl(\pi(m_1),\pi(m_2)\bigr).

这条公式把「模合数的周期」拆成了「模素数幂的周期」的最小公倍数。

拆开 π(10)=60\pi(10)=60

因为

10=2×5,gcd(2,5)=1, 10=2\times5, \qquad \gcd(2,5)=1,

所以

π(10)=lcm(π(2),π(5))=lcm(3,20)=60. \pi(10) = \operatorname{lcm}\bigl(\pi(2),\pi(5)\bigr) = \operatorname{lcm}(3,20) =60.

\operatorname{lcm}\bigl(\pi(2),\pi(5)\bigr)

\operatorname{lcm}(3,20)
=60.

6060 来自 332020 的最小公倍数。33 是模 22 的周期,2020 是模 55 的周期。

类似地:

π(100)=lcm(π(4),π(25))=lcm(6,100)=300. \pi(100) = \operatorname{lcm}\bigl(\pi(4),\pi(25)\bigr) = \operatorname{lcm}(6,100) =300.

\operatorname{lcm}\bigl(\pi(4),\pi(25)\bigr)

\operatorname{lcm}(6,100)
=300.

π(1000)=lcm(π(8),π(125))=lcm(12,500)=1500. \pi(1000) = \operatorname{lcm}\bigl(\pi(8),\pi(125)\bigr) = \operatorname{lcm}(12,500) =1500.

\operatorname{lcm}\bigl(\pi(8),\pi(125)\bigr)

\operatorname{lcm}(12,500)
=1500.

π(6)=lcm(π(2),π(3))=lcm(3,8)=24. \pi(6) = \operatorname{lcm}\bigl(\pi(2),\pi(3)\bigr) = \operatorname{lcm}(3,8) =24.

\operatorname{lcm}\bigl(\pi(2),\pi(3)\bigr)

\operatorname{lcm}(3,8)
=24.

π(15)=lcm(π(3),π(5))=lcm(8,20)=40. \pi(15) = \operatorname{lcm}\bigl(\pi(3),\pi(5)\bigr) = \operatorname{lcm}(8,20) =40.

\operatorname{lcm}\bigl(\pi(3),\pi(5)\bigr)

\operatorname{lcm}(8,20)
=40.

π(30)=lcm(π(2),π(3),π(5))=lcm(3,8,20)=120. \pi(30) = \operatorname{lcm}\bigl(\pi(2),\pi(3),\pi(5)\bigr) = \operatorname{lcm}(3,8,20) =120.

\operatorname{lcm}\bigl(\pi(2),\pi(3),\pi(5)\bigr)

\operatorname{lcm}(3,8,20)
=120.

只要知道素数幂的 Pisano 周期,就能通过分解模数来计算许多合数的周期。

素数幂的周期

现在问题变成:当 pp 是素数时,π(pk)\pi(p^k) 是多少?

2255,有两个常用规律:

π(2k)=32k1,k1. \pi(2^k)=3\cdot2^{k-1}, \qquad k\ge1.

π(5k)=45k,k1. \pi(5^k)=4\cdot5^k, \qquad k\ge1.

具体值是:

π(2)=3,π(4)=6,π(8)=12,π(16)=24. \pi(2)=3, \quad \pi(4)=6, \quad \pi(8)=12, \quad \pi(16)=24.

π(5)=20,π(25)=100,π(125)=500,π(625)=2500. \pi(5)=20, \quad \pi(25)=100, \quad \pi(125)=500, \quad \pi(625)=2500.

于是,例如

π(106)=π(2656)=lcm(π(26),π(56)). \pi(10^6) = \pi(2^6\cdot5^6) = \operatorname{lcm}\bigl(\pi(2^6),\pi(5^6)\bigr).

\pi(2^6\cdot5^6)

\operatorname{lcm}\bigl(\pi(2^6),\pi(5^6)\bigr).

代入 代入

代入

π(26)=325=96,π(56)=456=62500, \pi(2^6)=3\cdot2^5=96, \qquad \pi(5^6)=4\cdot5^6=62500,

得到

π(106)=lcm(96,62500)=1500000. \pi(10^6)=\operatorname{lcm}(96,62500)=1500000.

对许多素数 pp,从 pppkp^k 时,周期会按 pp 的幂增长。严格证明需要更多数论工具,本书这里只保留这个观察和它的用法。

素数 pp 的周期:和特征根有关

剩下的核心问题是:π(p)\pi(p) 是多少?

回忆第 8 章,Binet 公式来自特征方程

r2r1=0. r^2-r-1=0.

在模 pp 下,也可以研究这个方程。关键取决于 5\sqrt5 是否存在于模 pp 的世界里。

情况一:55 是模 pp 的二次剩余

这表示存在某个整数 xx,使得

x25(modp). x^2\equiv5\pmod p.

于是 5\sqrt5 可以在模 pp 下解释,φ\varphiψ\psi 也可以在模 pp 的有限域里解释。

这时 π(p)\pi(p)φ\varphiψ\psi 在乘法群中的阶有关,因此周期会整除 p1p-1。常写成:

π(p)p1,当 5 是模 p 的二次剩余。 \pi(p)\mid p-1, \qquad \text{当 }5\text{ 是模 }p\text{ 的二次剩余。}

二次互反律告诉我们,对奇素数 p5p\ne555 是模 pp 的二次剩余当且仅当

p±1(mod5). p\equiv\pm1\pmod5.

例子:

π(11)=10=111. \pi(11)=10=11-1.

π(19)=18=191. \pi(19)=18=19-1.

π(29)=14,1428=291. \pi(29)=14, \qquad 14\mid28=29-1.

情况二:55 是模 pp 的非二次剩余

这表示

p±2(mod5). p\equiv\pm2\pmod5.

这时 5\sqrt5 不在模 pp 的有限域里,而在它的二次扩域里。可以把它粗略理解为:要多引入一个类似 5\sqrt5 的新元素,才能写出两个特征根。

在这种情况下,周期整除 2(p+1)2(p+1)

π(p)2(p+1),当 5 是模 p 的非二次剩余。 \pi(p)\mid2(p+1), \qquad \text{当 }5\text{ 是模 }p\text{ 的非二次剩余。}

例子:

π(3)=8=2(3+1). \pi(3)=8=2(3+1).

π(7)=16=2(7+1). \pi(7)=16=2(7+1).

π(13)=28=2(13+1). \pi(13)=28=2(13+1).

π(23)=48=2(23+1). \pi(23)=48=2(23+1).

特殊情况:p=5p=5

p=5p=5 要单独处理。特征方程在模 55 下变成

r2r1r2r+4(mod5). r^2-r-1\equiv r^2-r+4\pmod5.

r2r+4(r3)2(mod5). r^2-r+4\equiv(r-3)^2\pmod5.

所以特征方程在模 55 下有重根 r3(mod5)r\equiv3\pmod5。这和前面两种情况都不一样。

事实是:

π(5)=20. \pi(5)=20.

它不是 p1=4p-1=4p+1=6p+1=6 的因子,而是

20=5(51). 20=5(5-1).

这种重根现象会让周期行为变得特殊。

二次互反律的位置

这一节用到了二次互反律的一个特例:

5 是模 p 的二次剩余p±1(mod5),p5. 5\text{ 是模 }p\text{ 的二次剩余} \Longleftrightarrow p\equiv\pm1\pmod5, \qquad p\ne5.

本书不证明二次互反律。证明需要不少准备,属于数论中级内容。这里只用它的结论来分类素数 pp,从而判断 π(p)\pi(p) 的整除范围。

这也说明:斐波那契数列的模周期问题并不只是初等算术。鸽巢原理只能告诉我们「一定循环」,具体周期是多少,会牵涉素数的深层结构。

一个应用:快速计算 FnmodmF_n\bmod m

知道 Pisano 周期后,要算 FnmodmF_n\bmod m,可以先把下标缩小:

Fnmodm=Fnmodπ(m)modm. F_n\bmod m=F_{n\bmod\pi(m)}\bmod m.

例子:计算

F1018mod10. F_{10^{18}}\bmod10.

因为

π(10)=60, \pi(10)=60,

所以只要算

1018mod60. 10^{18}\bmod60.

注意当 n2n\ge2 时,

10n40(mod60). 10^n\equiv40\pmod{60}.

因此

101840(mod60). 10^{18}\equiv40\pmod{60}.

于是

F1018mod10=F40mod10. F_{10^{18}}\bmod10=F_{40}\bmod10.

又因为

F40=102334155, F_{40}=102334155,

所以

F1018mod10=5. F_{10^{18}}\bmod10=5.

这里不需要写出 F1018F_{10^{18}}。这个数约有 2.09×10172.09\times10^{17} 位十进制数字,根本不适合直接展开。

整除性的另一个用法

模周期还能帮助判断 FnF_n 何时被某个数整除。

mm 下,Fn0(modm)F_n\equiv0\pmod m 的位置会形成周期结构。最小的正下标通常称为 mm 的 rank of apparition,记作 α(m)\alpha(m)。它满足

α(m)π(m). \alpha(m)\mid\pi(m).

例如

α(5)=5, \alpha(5)=5,

所以

5Fn5n. 5\mid F_n \Longleftrightarrow 5\mid n.

又如

α(10)=15, \alpha(10)=15,

所以 FnF_n 末位是 00 当且仅当 15n15\mid n。例子是:

F15=610,F30=832040,F45=1134903170. F_{15}=610, \quad F_{30}=832040, \quad F_{45}=1134903170.

它们的末位都是 00

本章小结

π(10)=60\pi(10)=60 并不神秘。它来自

lcm(π(2),π(5))=lcm(3,20)=60. \operatorname{lcm}\bigl(\pi(2),\pi(5)\bigr) = \operatorname{lcm}(3,20)=60.

\operatorname{lcm}(3,20)=60.
$$

中国剩余定理把模合数的周期拆成了模素数幂周期的最小公倍数。

素数 pp 的 Pisano 周期由 55 是否为模 pp 的二次剩余控制。若 p±1(mod5)p\equiv\pm1\pmod5,周期整除 p1p-1;若 p±2(mod5)p\equiv\pm2\pmod5,周期整除 2(p+1)2(p+1)p=5p=5 是重根特殊情况。

Pisano 周期的实际用处是把很大的下标缩小。计算 FnmodmF_n\bmod m 时,可以先用 nmodπ(m)n\bmod\pi(m) 替换 nn,再计算较小下标的斐波那契数。下一章我们换一个方向:用斐波那契数反过来表示任意整数。

第 19 章 每个整数都有自己的斐波那契写法

到上一章为止,我们把 FnF_n 当作"被算出来的数"和"被数出来的数"看过,还把它当作"整数"研究过它的整除性和模周期。

这一章换个方向:能不能用斐波那契数反过来表示任意整数?

听起来有点奇怪。1,2,3,5,8,13,21,1, 2, 3, 5, 8, 13, 21, \ldots 这串数怎么能表示所有正整数?比如 100100 怎么用斐波那契数之和表示?

从十进制和二进制说起

我们已经习惯用十进制表示整数。100=1102+0101+0100100 = 1 \cdot 10^2 + 0 \cdot 10^1 + 0 \cdot 10^0。每个位置上的数字是 0099,按位权 10k10^k 累加。

二进制也类似,100=64+32+4=26+25+22100 = 64 + 32 + 4 = 2^6 + 2^5 + 2^2,写成 110010021100100_2。每个位置上是 0011

这些表示系统有三个要素:基本元素(10k10^k2k2^k)、每个元素的系数(00990,10, 1)、组合规则(按位相加)。

Zeckendorf 表示也用这种思路,但是基本元素换成斐波那契数。

Zeckendorf 表示是什么

先用一个例子建立直觉。100100 怎么用斐波那契数之和表示?

按"贪心"策略:每一步取不超过剩余值的最大斐波那契数。

  • 100100 不超过的最大斐波那契数是 89=F1189 = F_{11}。剩余 10089=11100 - 89 = 11
  • 1111 不超过的最大斐波那契数是 8=F68 = F_6。剩余 118=311 - 8 = 3
  • 33 不超过的最大斐波那契数是 3=F43 = F_4。剩余 33=03 - 3 = 0。结束。

所以 100=89+8+3=F11+F6+F4100 = 89 + 8 + 3 = F_{11} + F_6 + F_4

注意一个细节:F11,F6,F4F_{11}, F_6, F_4 在斐波那契数列里不相邻。下标 11,6,411, 6, 4 之间都隔了至少 22

这不是巧合。这是 Zeckendorf 表示的关键特征。

Zeckendorf 定理:每个正整数都可以唯一表示为若干个不相邻斐波那契数之和。这个定理通常以比利时数学家 Edouard Zeckendorf 命名;Zeckendorf 在 1972 年发表相关论文,类似结果在更早的文献中也已经出现。

"不相邻"的意思是:在斐波那契数列里,被选中的数两两之间至少隔一个数。例如 F11+F6+F4F_{11} + F_6 + F_4 可以,因为下标 11,6,411, 6, 4 之间隔了至少 22F11+F10F_{11} + F_{10} 不行,因为下标 11,1011, 10 相邻。

注意我们用的是 F2=1,F3=2,F4=3,F5=5,F6=8,F_2 = 1, F_3 = 2, F_4 = 3, F_5 = 5, F_6 = 8, \ldots,跳过 F1=1F_1 = 1(避免重复的 11)。也就是说,Zeckendorf 表示用 {1,2,3,5,8,13,21,}\{1, 2, 3, 5, 8, 13, 21, \ldots\} 这一组数,11 只出现一次。

贪心算法为什么会停下

证明 Zeckendorf 定理分两半:存在性和唯一性。先看存在性。

存在性的证明本身就是构造性的,它给出贪心算法。我们对 nn 归纳。

n=1n = 1:取 F2=1F_2 = 1。一个数,没有不相邻问题。对。

假设对一切 k<nk < n 都成立。要证 nn 也能表示。

取最大的不超过 nn 的斐波那契数,记作 FmF_m。所以 Fmn<Fm+1F_m \le n < F_{m+1}

如果 n=Fmn = F_m,直接用 FmF_m 表示,结束。

如果 n>Fmn > F_m,让 n=nFmn' = n - F_m,那么 0<n<n0 < n' < n。由归纳假设,nn' 可以表示为不相邻斐波那契数之和:n=Fk1+Fk2++Fkrn' = F_{k_1} + F_{k_2} + \cdots + F_{k_r},其中 k1>k2>>krk_1 > k_2 > \cdots > k_r,且 kiki+12k_i - k_{i+1} \ge 2

那么 n=Fm+Fk1++Fkrn = F_m + F_{k_1} + \cdots + F_{k_r}

要检查这套表示满足"不相邻"。我们要证 mk12m - k_1 \ge 2,也就是 FmF_mFk1F_{k_1} 不相邻。

因为 Fk1n=nFm<Fm+1Fm=Fm1F_{k_1} \le n' = n - F_m < F_{m+1} - F_m = F_{m-1}。所以 Fk1<Fm1F_{k_1} < F_{m-1},即 Fk1Fm2F_{k_1} \le F_{m-2}(因为斐波那契数列递增),即 k1m2k_1 \le m - 2,即 mk12m - k_1 \ge 2。对。

所以存在性成立。这个证明本身就是算法:贪心算法。每次取最大的不超过剩余值的斐波那契数,递归处理。

为什么不会出现相邻项

贪心算法自动避免相邻项,这是它的关键性质。原因是上一步证明里那个不等式:Fk1Fm2F_{k_1} \le F_{m-2}

具体说,如果当前步取了 FmF_m,剩余值 n<Fm+1Fm=Fm1n' < F_{m+1} - F_m = F_{m-1}。下一步取的最大斐波那契数 n\le n',所以 Fm2\le F_{m-2}。下标差 2\ge 2

这个性质自上而下传递,所以贪心算法给出的表示天然满足"不相邻"约束。

唯一性:补一个短证明

唯一性需要一个小引理。

引理。F2,F3,,Fk1F_2,F_3,\ldots,F_{k-1} 中选若干项,要求下标不相邻。所有合法选择的和最多是

Fk1+Fk3+Fk5+=Fk1. F_{k-1}+F_{k-3}+F_{k-5}+\cdots=F_k-1.

这个引理可以用递推证明。设 AkA_k 表示从 F2F_2Fk1F_{k-1} 中选不相邻项时能得到的最大和。若想让和尽量大,先选最大的 Fk1F_{k-1}。选了它,就不能选 Fk2F_{k-2},剩下最多只能从 F2F_2Fk3F_{k-3} 中继续选,所以

Ak=Fk1+Ak2. A_k=F_{k-1}+A_{k-2}.

从小值开始检查,A2=0=F21A_2=0=F_2-1A3=F2=1=F31A_3=F_2=1=F_3-1。由递推可得

Ak=Fk1. A_k=F_k-1.

现在证明唯一性。假设某个正整数有两种不同的 Zeckendorf 表示。把两边共同出现的项先删掉,剩下的两边仍然相等,而且没有共同项。设剩下两边中最大的下标是 kk,不妨设左边含有 FkF_k。右边不能含有 FkF_k,只能用下标小于 kk 的项来补。

但根据刚才的引理,右边所有下标小于 kk 且互不相邻的项,加起来最多是 Fk1F_k-1,不可能等于或超过左边的 FkF_k。矛盾。

所以 Zeckendorf 表示唯一。

一个具体例子:从 1 到 20

112020 都表示出来,让你直观感受这种"进制"。

nn Zeckendorf 表示 nn Zeckendorf 表示
11 F2F_2 1111 F6+F4F_6 + F_4
22 F3F_3 1212 F6+F4+F2F_6 + F_4 + F_2
33 F4F_4 1313 F7F_7
44 F4+F2F_4 + F_2 1414 F7+F2F_7 + F_2
55 F5F_5 1515 F7+F3F_7 + F_3
66 F5+F2F_5 + F_2 1616 F7+F4F_7 + F_4
77 F5+F3F_5 + F_3 1717 F7+F4+F2F_7 + F_4 + F_2
88 F6F_6 1818 F7+F5F_7 + F_5
99 F6+F2F_6 + F_2 1919 F7+F5+F2F_7 + F_5 + F_2
1010 F6+F3F_6 + F_3 2020 F7+F5+F3F_7 + F_5 + F_3

观察一些规律:

每个数对应的 Zeckendorf 表示里没有相邻斐波那契数。例如 12=F6+F4+F212 = F_6 + F_4 + F_2,下标 6,4,26, 4, 2 都隔了 22

每个数的 Zeckendorf 表示是唯一的。比如 2020 不能表示成 F7+F5+F3F_7 + F_5 + F_3 之外的形式(满足不相邻)。

最大项决定数量级。例如 13132020 都以 F7F_7 开头,因为 F7=13F_7 = 13 是不超过它们的最大斐波那契数。

和二进制对比

性质 二进制 Zeckendorf
基本元素 22 的幂:1,2,4,8,16,1, 2, 4, 8, 16, \ldots 斐波那契数:1,2,3,5,8,13,1, 2, 3, 5, 8, 13, \ldots
每个元素的次数 0011 0011
约束 不相邻(相邻不能同时为 11
唯一性
进位规则 22k=2k+12 \cdot 2^k = 2^{k+1} Fi+Fi+1=Fi+2F_i + F_{i+1} = F_{i+2};重复项 2Fi=Fi+1+Fi22 F_i = F_{i+1} + F_{i-2}

Zeckendorf 表示比二进制"松"一点。基本元素更密(斐波那契数比 22 的幂多),约束更紧(不能相邻)。

Zeckendorf 表示通常比二进制表示长。原因是斐波那契数增长慢于 22 的幂,所以表示同一个数时,最高位位置往往更靠后。比如 100100 的二进制需要 77 位,而它的 Zeckendorf 表示从 F2F_2F11F_{11} 需要 1010 个位置。

Zeckendorf 表示和 Fibonacci coding 不是同一件事

这两个概念容易混淆,要区分清楚。

Zeckendorf 表示是一种整数分解方式,它把整数写成若干个不相邻斐波那契数之和。它本身是一组数学对象,没有"编码"或"传输"的功能。

Fibonacci coding 是一种编码方法。它通常把 Zeckendorf 表示的位串反向,再在末尾追加一个 11,于是编码串以 1111 结束。这个 1111 是结束标记,让多个码字可以无分隔符地拼接。

因此,以 1111 结束的是 Fibonacci coding 的编码串,不是 Zeckendorf 表示本身。

Zeckendorf 表示的加法规则:

两个 Zeckendorf 表示的数相加,先按位相加,允许系数为 0,1,20, 1, 2,再"归一化"处理。归一化有两类规则。第一类处理相邻项:Fi+Fi+1=Fi+2F_i + F_{i+1} = F_{i+2}。第二类处理重复项:2Fi=Fi+1+Fi22 F_i = F_{i+1} + F_{i-2}(因为 Fi+1=Fi+Fi1F_{i+1} = F_i + F_{i-1}Fi=Fi1+Fi2F_i = F_{i-1} + F_{i-2},所以 Fi+1+Fi2=Fi+Fi1+Fi2=Fi+Fi=2FiF_{i+1} + F_{i-2} = F_i + F_{i-1} + F_{i-2} = F_i + F_i = 2 F_i)。小下标情形需要单独处理,因为 Fi2F_{i-2}i<2i < 2 时会越界。反复应用这两类规则,直到没有相邻 11 也没有系数 22,得到合法的 Zeckendorf 表示。

比较两个 Zeckendorf 表示的数的大小,按位从高到低比较,第一个不同位决定大小。

Lucas 数列提醒我们什么

Zeckendorf 表示用斐波那契数。如果换成 Lucas 数列 LnL_n,它同样满足递推

Ln=Ln1+Ln2, L_n=L_{n-1}+L_{n-2},

但初值变成

L0=2,L1=1. L_0=2, \qquad L_1=1.

Lucas 数列也可以用于构造表示系统,不过它需要额外约定,不能直接照搬 Zeckendorf 定理的表述。这里不展开 Lucas 表示理论,只把它作为提醒:换一个初值,许多看似相同的性质都会改变。

斐波那契的 Zeckendorf 表示唯一性依赖一套具体约定,包括使用 F2=1,F3=2,F4=3,F_2=1,F_3=2,F_4=3,\ldots 作为基底,并禁止相邻下标同时出现。离开这些约定,表示系统就需要重新证明。

本章小结

Zeckendorf 定理:每个正整数都可以唯一表示为若干个不相邻斐波那契数之和。

证明思路:贪心算法给出存在性(每次取最大不超过剩余值的斐波那契数),斐波那契求和引理给出唯一性。

Zeckendorf 表示可以当作一种"进制",叫斐波那契进制。和二进制相比,基本元素更密(斐波那契数比 22 的幂多),约束更紧(不能相邻)。表示通常更长,但 Fibonacci coding 编码后会以 1111 结束,可用于无分隔符压缩。

Zeckendorf 表示的唯一性是斐波那契特有的,不是所有线性递推都有。Lucas 表示需要额外约定,不能直接照搬 Zeckendorf 定理。

这件事让斐波那契数列在"表示整数"这个角度上有了新的角色。它不再只是"被算出来的数"或"被数出来的数",它本身可以作为"基底"反过来表示其他整数。这是斐波那契数列的另一面。

下一章我们换一个完全不同的场景:把斐波那契搬到概率里。看抛硬币这种最随机的场景里,斐波那契怎么会跑出来当计数员。

第 20 章 概率里的斐波那契:抛硬币不出现连续 HH

到这一章为止,斐波那契数列给人的印象偏向确定性。递推确定,算法确定,矩阵确定,整除性确定,模周期确定,Zeckendorf 表示也确定。

这一章把它带进概率里。概率看起来是不确定的:抛硬币出现正面还是反面,每次都不一样。但只要问题里出现「不允许某个局部模式」,计数结构就会重新出现,斐波那契数列也会跟着出现。

抛硬币不出现连续两个正面

nn 次公平硬币,每次结果是 HHTT。问:长度为 nn 的所有结果串中,有多少个不出现连续两个 HH

设这个数为 CnC_n

先看小值:

  • n=0n=0:只有空串,所以 C0=1C_0=1
  • n=1n=1H,TH,T,所以 C1=2C_1=2
  • n=2n=2HH,HT,TH,TTHH,HT,TH,TT,去掉 HHHH,所以 C2=3C_2=3
  • n=3n=388 种里去掉 HHH,HHT,THHHHH,HHT,THH,所以 C3=5C_3=5
  • n=4n=4:继续数下去,得到 C4=8C_4=8

于是

C0,C1,C2,C3,C4,=1,2,3,5,8, C_0,C_1,C_2,C_3,C_4,\ldots=1,2,3,5,8,\ldots

这就是斐波那契数列平移后的样子:

Cn=Fn+2. C_n=F_{n+2}.

为什么满足斐波那契递推

按最后一位分类。

如果最后一位是 TT,前面 n1n-1 位可以是任意合法串,方法数是 Cn1C_{n-1}

如果最后一位是 HH,为了不出现 HHHH,倒数第二位必须是 TT。也就是说,合法串末尾形如 THTH,前面 n2n-2 位可以是任意合法串,方法数是 Cn2C_{n-2}

两类不重不漏,所以

Cn=Cn1+Cn2. C_n=C_{n-1}+C_{n-2}.

再加上 C0=1,C1=2C_0=1,C_1=2,得到

Cn=Fn+2. C_n=F_{n+2}.

这和铺砖、楼梯、不连续子集、二进制串是同一个结构。把 HH 当作 11,把 TT 当作 00,不出现连续 HH 就是不出现相邻 11

概率是多少

所有长度为 nn 的硬币串共有 2n2^n 个,合法串有 Cn=Fn+2C_n=F_{n+2} 个。因此,不出现连续两个正面的概率为

Pn=Cn2n=Fn+22n. P_n=\frac{C_n}{2^n}=\frac{F_{n+2}}{2^n}.

一些具体值是:

P1=22=1,P2=34=0.75,P3=58=0.625,P4=816=0.5,P5=13320.406,P10=14410240.141,P20=1771110485760.0169,P50=F522503295128009911258999068426242.93×105. \begin{aligned} P_1&=\frac22=1,\\ P_2&=\frac34=0.75,\\ P_3&=\frac58=0.625,\\ P_4&=\frac8{16}=0.5,\\ P_5&=\frac{13}{32}\approx0.406,\\ P_{10}&=\frac{144}{1024}\approx0.141,\\ P_{20}&=\frac{17711}{1048576}\approx0.0169,\\ P_{50}&=\frac{F_{52}}{2^{50}}\approx\frac{32951280099}{1125899906842624}\approx2.93\times10^{-5}. \end{aligned}

随着 nn 增大,PnP_n 趋向 00。这符合直觉:抛得越久,越容易出现连续正面。

它趋向 00 的速度也能从斐波那契数列看出来。由于

Fn+2φn+25, F_{n+2}\approx\frac{\varphi^{n+2}}{\sqrt5},

所以

Pnφn+252n=(φ2)nφ25. P_n\approx\frac{\varphi^{n+2}}{\sqrt5\cdot2^n} =\left(\frac{\varphi}{2}\right)^n\cdot\frac{\varphi^2}{\sqrt5}.

因为

φ20.809<1, \frac{\varphi}{2}\approx0.809<1,

所以 PnP_n 以大约 0.809n0.809^n 的速度衰减。这里的 0.8090.809 不是随便来的,它正是斐波那契增长率 φ\varphi 和全部硬币串增长率 22 的比值。

反过来:出现连续两个正面的概率

出现至少一次连续两个正面的概率是

1Pn=1Fn+22n. 1-P_n=1-\frac{F_{n+2}}{2^n}.

比如:

n=1:0,n=2:14,n=3:38,n=4:12,n=5:19320.594,n=10:10.1410.859,n=20:10.01690.983. \begin{aligned} n=1 &: 0,\\ n=2 &: \frac14,\\ n=3 &: \frac38,\\ n=4 &: \frac12,\\ n=5 &: \frac{19}{32}\approx0.594,\\ n=10 &: 1-0.141\approx0.859,\\ n=20 &: 1-0.0169\approx0.983. \end{aligned}

n=20n=20,几乎肯定会出现连续两个正面。这有点像生日悖论:随着试验次数增加,某种局部碰撞会迅速变得很难避免。

Fibonacci coding 的密度

上一章提到 Fibonacci coding:它把整数编码成 0/10/1 串,除了作为结束标记的末尾 1111 之外,主体部分不含相邻 11

这件事和本章的概率问题相关。长度为 nn 的所有二进制串共有 2n2^n 个,其中不含相邻 11 的串只有 Fn+2F_{n+2} 个。所以如果把所有长度为 nn 的二进制串看作空间,符合 Fibonacci coding 主体约束的串只占

Fn+22n=Pn. \frac{F_{n+2}}{2^n}=P_n.

nn 很大时,这个比例趋向 00。也就是说,Fibonacci coding 的主体约束牺牲了编码密度,换来的是自同步和无分隔符读取的便利。

一个稍微复杂的例子:抛骰子不出现连续两个 6

把问题稍作推广。抛一枚均匀骰子 nn 次,问不出现连续两个 66 的概率。

设合法序列数为 DnD_n

小值为:

D0=1,D1=6,D2=35. D_0=1,\qquad D_1=6,\qquad D_2=35.

按末尾分类。

如果末尾不是 66,最后一位有 55 种选择,前面 n1n-1 位是任意合法序列,方法数是 5Dn15D_{n-1}

如果末尾是 66,倒数第二位不能是 66,所以末尾形如 X6X6,其中 X{1,2,3,4,5}X\in\{1,2,3,4,5\}。前面 n2n-2 位是任意合法序列,方法数是 5Dn25D_{n-2}

因此

Dn=5Dn1+5Dn2. D_n=5D_{n-1}+5D_{n-2}.

概率为

Pn=Dn6n. P_n=\frac{D_n}{6^n}.

例如

P2=35360.972. P_2=\frac{35}{36}\approx0.972.

这个递推的特征方程是

r2=5r+5, r^2=5r+5,

也就是

r25r5=0. r^2-5r-5=0.

较大根为

5+452=5+3525.854. \frac{5+\sqrt{45}}2=\frac{5+3\sqrt5}{2}\approx5.854.

所以大致有

Dn5.854n,Pn(5.8546)n0.976n. D_n\approx5.854^n, \qquad P_n\approx\left(\frac{5.854}{6}\right)^n\approx0.976^n.

这个例子说明,「不出现连续两个指定符号」不只在硬币里出现。换成更大的字母表,递推还在,只是系数和增长率变了。

一个推广方向:禁止更长的连续正面

如果不允许出现连续 kk 个正面,合法序列数设为 Dn,kD_{n,k}

按末尾连续 HH 的个数分类,合法串可以以

T,HT,HHT,,Hk1T T, \quad HT, \quad HHT, \quad \ldots, \quad H^{k-1}T

结尾。于是对 nkn\ge k

Dn,k=Dn1,k+Dn2,k++Dnk,k. D_{n,k}=D_{n-1,k}+D_{n-2,k}+\cdots+D_{n-k,k}.

k=2k=2 时,这就是斐波那契递推。当 k=3k=3 时,得到 Tribonacci 型递推:

Dn,3=Dn1,3+Dn2,3+Dn3,3, D_{n,3}=D_{n-1,3}+D_{n-2,3}+D_{n-3,3},

初值为

D0,3=1,D1,3=2,D2,3=4. D_{0,3}=1,\qquad D_{1,3}=2,\qquad D_{2,3}=4.

下一章会继续这个方向,但问题换成「首次出现连续两个正面,平均要等多久」。那时我们不再固定抛 nn 次,而是用状态法计算等待时间。

本章小结

抛硬币不出现连续两个正面的合法序列数是 Fn+2F_{n+2}。它和铺砖、楼梯、不连续子集、二进制串共享同一个局部禁止结构。

概率

Pn=Fn+22n P_n=\frac{F_{n+2}}{2^n}

的衰减速度由 φ/20.809\varphi/2\approx0.809 决定。这个数看起来属于概率,其实来自斐波那契数列的增长率。

推广到「不出现连续 kk 个正面」会得到 kk 阶递推。斐波那契只是其中 k=2k=2 的情形。

第 21 章 首次 HH 的期望与 kk-bonacci 推广

上一章算的是「抛 nn 次硬币不出现连续两个正面」的概率。这一章换一个相关问题:一直抛硬币,直到第一次出现连续两个正面。平均要抛多少次?

设这个期望为 EE。这是一个经典随机过程问题,可以用状态法解。

状态法分析

考虑三种状态:

  • S0S_0:当前还没有连续正面,且上一次不是 HH,或者刚开始。
  • S1S_1:当前还没有连续正面,且上一次是 HH
  • 吸收态:已经出现 HHHH

E0E_0 是从 S0S_0 出发的期望步数,E1E_1 是从 S1S_1 出发的期望步数。

S0S_0 出发,抛一次后:以 1/21/2 概率到 S1S_1,以 1/21/2 概率回到 S0S_0。所以

E0=1+12E1+12E0. E_0=1+\frac{1}{2}E_1+\frac{1}{2}E_0.

S1S_1 出发,抛一次后:以 1/21/2 概率成功,期望剩余步数为 00;以 1/21/2 概率回到 S0S_0。所以

E1=1+120+12E0. E_1=1+\frac{1}{2}\cdot0+\frac{1}{2}E_0.

第一个方程化简为

E0=2+E1. E_0=2+E_1.

第二个方程是

E1=1+12E0. E_1=1+\frac{1}{2}E_0.

代入得到

E0=2+1+12E0. E_0=2+1+\frac{1}{2}E_0.

所以

12E0=3,E0=6. \frac{1}{2}E_0=3, \qquad E_0=6.

再代回去:

E1=1+126=4. E_1=1+\frac{1}{2}\cdot6=4.

因此,从开始状态出发,平均要抛 66 次才会首次出现连续两个正面。

验证一下

先看前几项是否合理。

22 次时,首次 HHHH 出现在末尾的序列只有

HH, HH,

概率是

14. \frac14.

33 次时,末尾必须是 HHHH,前面不能已经出现 HHHH。合法序列是

THH, THH,

概率是

18. \frac18.

44 次时,首次 HHHH 出现在最后两位,合法序列是

TTHHHTHH. TTHH \quad\text{和}\quad HTHH.

概率是

216=18. \frac2{16}=\frac18.

期望也可以写成无穷和:

E=n2nP(首次 HH 在第 n 步). E= \sum_{n\ge2}n\cdot P(\text{首次 }HH\text{ 在第 }n\text{ 步}).

这个求和可以算,但状态法更直接。前几项贡献是

214+318+418+. 2\cdot\frac14+3\cdot\frac18+4\cdot\frac18+\cdots.

最终结果是 66,与状态法一致。

一个直觉解释:为什么是 6

每次处在 S0S_0,要先等到一个 HH。抛出 HH 的概率是 1/21/2,所以平均要等 22 步进入 S1S_1

进入 S1S_1 后,再抛一次。若抛出 HH,成功;若抛出 TT,回到 S0S_0,重新开始。

S1S_1 出发的期望是

E1=121+12(1+E0)=1+12E0. E_1=\frac{1}{2}\cdot1+\frac{1}{2}(1+E_0)=1+\frac{1}{2}E_0.

S0S_0 出发则是

E0=2+E1. E_0=2+E_1.

代入仍然得到

E0=6,E1=4. E_0=6, \qquad E_1=4.

等待 HHHH 的过程里有「推进一步」和「失败重来」两个动作。状态法把这个结构写成方程。

推广:等到连续 kk 个正面

把问题推广:一直抛硬币,直到第一次出现连续 kk 个正面,也就是 HkH^k。平均要抛几次?

这次状态更多:

S0,S1,S2,,Sk1,Sk. S_0,S_1,S_2,\ldots,S_{k-1},S_k.

其中 SiS_i 表示当前末尾已有连续 iiHH,但还没有达到 kk 个。SkS_k 是吸收态。

EiE_i 是从 SiS_i 出发的期望步数。对 0i<k0\le i<k,有

Ei=1+12Ei+1+12E0, E_i=1+\frac{1}{2}E_{i+1}+\frac{1}{2}E_0,

并且

Ek=0. E_k=0.

从后往前解,可以得到通用公式:

E0=2k+12. E_0=2^{k+1}-2.

具体值如下:

k等待模式E01H222=22HH232=63HHH242=144HHHH252=305HHHHH262=62 \begin{array}{c|c|c} k&\text{等待模式}&E_0\\ \hline 1&H&2^{2}-2=2\\ 2&HH&2^{3}-2=6\\ 3&HHH&2^{4}-2=14\\ 4&HHHH&2^{5}-2=30\\ 5&HHHHH&2^{6}-2=62 \end{array}

所以等到连续 55 个正面,平均要抛 6262 次。这个数字比 1/(1/32)=321/(1/32)=32 大,是因为很多看似接近成功的尝试会被一个 TT 打断,然后重新开始。

概率和 kk-bonacci 的关系

上一章讨论过「不出现连续 kk 个正面」的合法序列数。令

Dn,k=长度为 n 的 H/T 串中,不出现连续 k 个 H 的串数. D_{n,k}= \text{长度为 }n\text{ 的 }H/T\text{ 串中,不出现连续 }k\text{ 个 }H\text{ 的串数}.

它满足 kk 阶递推:

Dn,k=Dn1,k+Dn2,k++Dnk,k. D_{n,k}=D_{n-1,k}+D_{n-2,k}+\cdots+D_{n-k,k}.

这一章的等待时间问题给出的是

Ek=2k+12. E_k=2^{k+1}-2.

两件事看起来不同,但都来自同一个局部约束:「不能过早出现连续 kkHH」。

Dn,kD_{n,k} 数的是前 nn 次里有多少种合法序列。EkE_k 问的是首次不合法平均出现在什么时候。两者可以通过首次出现的位置联系起来:

Ek=nknP(首次出现 Hk 在第 n 步). E_k= \sum_{n\ge k} n\cdot P(\text{首次出现 }H^k\text{ 在第 }n\text{ 步}).

本书不展开这个概率分布的完整推导。这里保留关系即可:合法串计数给出 kk-bonacci,等待时间给出 2k+122^{k+1}-2

kk-bonacci:要区分两个对象

这里容易混淆两个相关但不同的对象。

对象一:避免连续 kk 个正面的合法串数

定义:

Dn,k=长度为 n 的 H/T 串中,不出现连续 k 个 H 的串数. D_{n,k}= \text{长度为 }n\text{ 的 }H/T\text{ 串中,不出现连续 }k\text{ 个 }H\text{ 的串数}.

初值是:

D0,k=1,Dn,k=2n(1n<k). D_{0,k}=1, \qquad D_{n,k}=2^n\quad(1\le n<k).

因为当 n<kn<k 时,不可能出现连续 kkHH,所有串都合法。

nkn\ge k 时,按末尾连续 HH 的个数分类,得到

Dn,k=Dn1,k+Dn2,k++Dnk,k. D_{n,k}=D_{n-1,k}+D_{n-2,k}+\cdots+D_{n-k,k}.

例如 k=3k=3 时:

D0,3,D1,3,D2,3,D3,3,D4,3,=1,2,4,7,13, D_{0,3},D_{1,3},D_{2,3},D_{3,3},D_{4,3},\ldots = 1,2,4,7,13,\ldots

1,2,4,7,13,\ldots

对象二:某种约定下的 kk-bonacci 数列

不同书对 kk-bonacci 的初值约定并不完全一样。常见的一种约定是

an,k=an1,k+an2,k++ank,k, a_{n,k}=a_{n-1,k}+a_{n-2,k}+\cdots+a_{n-k,k},

并设

a0,k=a1,k==ak1,k=1. a_{0,k}=a_{1,k}=\cdots=a_{k-1,k}=1.

按这个约定:

k=2 (Fibonacci):       1, 1, 2, 3, 5, 8, 13, 21, ...
k=3 (Tribonacci):      1, 1, 1, 3, 5, 9, 17, 31, 57, ...
k=4 (Tetranacci):      1, 1, 1, 1, 4, 7, 13, 25, 49, ...

注意,这个标准 kk-bonacci 和 Dn,kD_{n,k} 不完全一样,因为初值不同。标准 kk-bonacci 在 0n<k0\le n<k 时都取 11,而 Dn,kD_{n,k}1n<k1\le n<k 时取 2n2^n

本书在概率问题里使用的是 Dn,kD_{n,k}。提到 kk-bonacci 时,应当明确所采用的初值约定。

哪些性质可以推广

许多结构可以从斐波那契推广到 kk-bonacci,例如:

  • 矩阵形式:用 k×kk\times k 伴随矩阵表示状态转移;
  • 闭式公式:用特征根的线性组合描述通项;
  • 组合解释:用 1,2,,k1,2,\ldots,k 格砖铺走廊;
  • 模周期:模 mm 下的状态向量长度为 kk,状态空间上界为 mkm^k

但有些性质未必保留:

  • 整除性:FmFnmnF_m\mid F_n\Longleftrightarrow m\mid n 这种结构依赖斐波那契的具体初值;
  • 唯一表示:Zeckendorf 表示的唯一性是斐波那契的特殊性质;
  • 素性:Fibonacci 素数的研究依赖斐波那契数列本身,不能简单搬到一般 kk-bonacci 上。

所以,斐波那契数列是线性递推的一个清楚样本。它展示了哪些结构可以推广,也提醒我们:并非所有性质都会跟着保留。

一个稍微深的问题:Fibonacci 素数

最后看一个开放方向。

斐波那契数中有些是素数:

F3=2,F4=3,F5=5,F7=13,F11=89,F13=233,F17=1597, F_3=2, \quad F_4=3, \quad F_5=5, \quad F_7=13, \quad F_{11}=89, \quad F_{13}=233, \quad F_{17}=1597, \ldots

这些数叫 Fibonacci 素数。

注意,下标 nn 大多是素数,例外是 n=4n=4。这和第 16 章的整除性定理有关:当 m3m\ge3 时,

FmFnmn. F_m\mid F_n \Longleftrightarrow m\mid n.

如果 nn 是合数,写成 n=mkn=mk,其中 m3m\ge3,那么

FmFn. F_m\mid F_n.

这通常会让 FnF_n 成为合数。因此,若 FnF_n 是素数,则 nn 必须是素数,或者是特殊下标 n=4n=4

反过来不成立。比如

F19=4181=37×113, F_{19}=4181=37\times113,

虽然 1919 是素数,但 F19F_{19} 不是素数。

是否存在无穷多个 Fibonacci 素数?这是数论中的未解问题。本书不继续展开,只把它作为斐波那契数列通向更深数论的一个入口。

本章小结

等到首次出现连续两个正面,平均要抛 66 次硬币。一般地,等到首次出现连续 kk 个正面的期望是

2k+12. 2^{k+1}-2.

不出现连续 kk 个正面的合法序列数满足 kk 阶线性递推。它和等待时间问题都来自同一个局部限制:连续 kkHH 不能过早出现。

kk-bonacci 保留了斐波那契的一部分结构,比如矩阵形式、闭式公式、组合解释和模周期。但整除性、唯一表示、素性等性质未必保留,需要分别讨论。

到这里,斐波那契数列在概率里的角色告一段落。尾声会把视角再放宽:哪些性质属于一般线性递推,哪些性质是斐波那契特有的。

尾声:斐波那契不是终点

这本书写完了。我们一起把斐波那契数列从 0, 1, 1, 2, 3, 5, 8, 13 这八个数开始,一路看下去。看到递归、动态规划、矩阵快速幂、Binet 公式、铺砖、图、整除性、Pisano 周期、抛硬币概率、Zeckendorf 表示、kk-bonacci 推广。

回头看,做了什么?

我们没有"穷尽"斐波那契数列。还有大量内容没碰:Zeckendorf 表示的高阶应用、Fibonacci heap 数据结构、Fibonacci 码的工程实现、负指标下的 FnF_{-n}、Fibonacci 素数的未解问题、Fibonacci 数列和黄金比例的几何关联、与 Lucas 序列的精细关系、模形式和 L 函数的深层链接。这些都可以单独写一本书。

我们做的,是另一种事:把同一个对象,反复换语言看。

换语言这件事

把全书"换语言"的时刻列一下:

第 1 章:从故事(兔子、楼梯、铺砖)换成递推。
第 2 章:从递推换成递归程序。
第 3 章:从递归程序换成调用树,分析复杂度。
第 4 章:从朴素递归换成记忆化表 / 自底向上迭代。
第 5 章:从一步一加法换成翻倍跳步(快速倍增)。
第 6 章:从代数恒等式换成矩阵快速幂。
第 7 章:从矩阵作为工具换成矩阵作为状态语言。
第 8 章:从整数加法换成特征根的指数(Binet 公式)。
第 9 章:从 Binet 公式的"5\sqrt{5} 怎么进入"换成"5\sqrt{5} 怎么消失"。
第 10 章:从被算出来的数换成被数出来的对象(铺砖、楼梯)。
第 11 章:从铺砖换成更多计数外衣(子集、二进制串)。
第 12 章:从一维位置序列换成图上的点和线。
第 13 章:从恒等式换成代数证法。
第 14 章:从代数证法换成组合证法。
第 15 章:从数列本身换成生成函数。
第 16 章:从数列的值换成下标的算术结构。
第 17 章:从无限序列换成有限状态系统的循环。
第 18 章:从 Pisano 周期的现象换成它的素数结构。
第 19 章:从斐波那契数作为"被表示的对象"换成作为"表示的基底"。
第 20 章:从确定性问题换成概率里的合法计数。
第 21 章:从概率的"避免模式"换成"等待首次模式",推广到 kk-bonacci。

每一次换语言,都看到一些新的东西。代数看见递推结构。算法看见子问题重叠。矩阵看见线性变换。闭式看见增长模式。组合看见计数对象。图论看见形状。数论看见算术镜像。模运算看见有限状态。概率看见合法路径。一般化看见一整族同类问题。

每一次换语言,也都"漏掉"一些东西。代数看不见计算复杂度。算法看不见组合意义。矩阵看不见闭式的简洁。闭式看不见整除性。组合看不见模周期。图论看不见增长速度。数论看不见算法效率。模运算看不见具体大值。概率看不见确定性结构。一般化看不见斐波那契特有的性质。

没有一种语言是"最好的"。每种语言适合回答某类问题,不适合回答另一类。

学数学的一个重要部分,就是学会在不同语言之间切换。看见一个问题,先问"这个问题在哪种语言里最自然"。如果原问题在代数里,但是组合解释更清楚,那就翻译到组合。如果原问题在算法里,但是矩阵形式更紧凑,那就翻译到矩阵。如果原问题在整数里,但是特征根在扩展域里更顺,那就翻译到扩展域。

这种"换语言"的意识,比任何具体的技巧都重要。技巧可以查,意识必须练。

一个最后的例子

收尾前,再看一个换语言的例子。

考虑恒等式:

F2n=Fn(2Fn+1Fn). F_{2n} = F_n \cdot (2 F_{n+1} - F_n).

第 5 章我们推过它。当时用加法公式 Fn+m=Fn+1Fm+FnFm1F_{n+m} = F_{n+1} F_m + F_n F_{m-1},让 m=nm = n,再做一点代数变形。

第 7 章矩阵视角下,这个恒等式是 (Mn)2=M2n(M^n)^2 = M^{2n} 的右上角 entry。具体来说:

Mn=(Fn+1FnFnFn1), M^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix},

(Mn)2=(Fn+12+Fn2Fn+1Fn+FnFn1), (M^n)^2 = \begin{pmatrix} F_{n+1}^2 + F_n^2 & F_{n+1} F_n + F_n F_{n-1} \\ \cdots & \cdots \end{pmatrix},

M2n=(F2n+1F2nF2nF2n1). M^{2n} = \begin{pmatrix} F_{2n+1} & F_{2n} \\ F_{2n} & F_{2n-1} \end{pmatrix}.

对比右上角:F2n=Fn+1Fn+FnFn1=Fn(Fn+1+Fn1)=Fn(2Fn+1Fn)F_{2n} = F_{n+1} F_n + F_n F_{n-1} = F_n (F_{n+1} + F_{n-1}) = F_n (2 F_{n+1} - F_n)

第 10 章组合视角下,这个恒等式可以解释为:长度 2n12n-1 走廊的铺法数 = 按中间位置 nn 的状态分类,三类铺法数之和。三类分别是"中间是 1 格砖"、"中间是 2 格砖跨过 n1n-1nn"、"中间是 2 格砖跨过 nnn+1n+1"。三类加起来给出 Fn2+2FnFn1=Fn(Fn+2Fn1)=Fn(2Fn+1Fn)F_n^2 + 2 F_n F_{n-1} = F_n (F_n + 2 F_{n-1}) = F_n (2 F_{n+1} - F_n)

第 8 章闭式视角下,这个恒等式变成:

φ2nψ2n5=φnψn5(2φn+1ψn+15φnψn5). \frac{\varphi^{2n} - \psi^{2n}}{\sqrt{5}} = \frac{\varphi^n - \psi^n}{\sqrt{5}} \cdot \left( 2 \cdot \frac{\varphi^{n+1} - \psi^{n+1}}{\sqrt{5}} - \frac{\varphi^n - \psi^n}{\sqrt{5}} \right).

化简后两边都是 (φ2nψ2n)/5(\varphi^{2n} - \psi^{2n}) / \sqrt{5},所以相等。这个证明看起来"显然",但是依赖于 φ\varphiψ\psi 满足特征方程 r2=r+1r^2 = r + 1

第 15 章生成函数视角下,这个恒等式可以从 G(x2)G(x^2)G(x)G(x) 的关系推出来,但是推导比较繁琐。

同一个恒等式,至少四种证法:代数(用加法公式)、矩阵(看 M2nM^{2n} 的 entry)、组合(按中间位置分类计数)、闭式(用 φ,ψ\varphi, \psi 的代数性质)。

四种证法给出四种理解:

代数证法告诉你这个恒等式来自加法公式,加法公式本身来自递推的线性结构。
矩阵证法告诉你这个恒等式是"矩阵平方"的一个 entry,矩阵平方对应"翻倍"。
组合证法告诉你这个恒等式是"按中间位置分类计数"的结果。
闭式证法告诉你这个恒等式是特征方程 r2=r+1r^2=r+1的代数后果。

四种理解加起来,比任何一种都更完整。这就是换语言的力量。同一条事实,从四个角度看,看到四个不同的"为什么"。

这本书没有做的事

最后说几句这本书没有做的事。

没有把黄金比例神秘化。φ\varphi 出现在 Binet 公式里,因为它满足 r2=r+1r^2=r+1。这个方程的解是 (1 ±\pm 5\sqrt{5})/2。φ\varphi 不是宇宙密码,它是一个二次方程的较大根。所有"φ\varphi 在自然界无处不在"的说法,要么是把这个代数事实夸大了,要么是把别的比例(比如 1.4, 1.5)也算成"接近 φ\varphi"凑出来的。

没有把斐波那契数列神秘化。斐波那契数列确实反复出现在不同数学分支里,但这种反复有具体原因。它对应最简单的二阶线性递推,这种结构在很多场景里都会自然出现。这是结构必然。

没有把数学写成知识点清单。这本书的章节顺序不是按"知识点难度"排的,而是按"换语言的顺序"排的。每一章换一种语言,看同一种结构在新语言里是什么样。这种顺序牺牲了某些系统性(比如恒等式没有集中列在某一章,而是散落在各章里),但换来了"换语言"这条主线的清晰。

没有承诺看完就懂数学。看完这本书,你不会变成数学家,也不会变成算法专家。但是你应该比以前更习惯一件事:看见一个数学对象,会问"它能在几种语言里被描述"。这种习惯比任何具体知识都重要。

一个最后的画面

回到开头的画面。一串很乖的整数 0, 1, 1, 2, 3, 5, 8, 13, …。

它本来只是"前两项加起来得下一项"。但是多看几眼,它开始到处开门。

我们推开过 21 扇门。

第一扇门里,它从兔子、楼梯、铺砖三个故事里分别长出来。
第二扇门里,它被翻译成一段递归程序。
第三扇门里,那棵调用树长得像斐波那契分形。
第四扇门里,那棵树被一张备忘录压扁成一条链。
第五扇门里,加法变成了乘法,k 步压成 1 步。
第六扇门里,矩阵帮忙做同样的跳步。
第七扇门里,矩阵变成了状态变化的语言。
第八扇门里,5\sqrt{5} 出现了。
第九扇门里,5\sqrt{5} 消失了。
第十扇门里,它变成了五块真实的砖头。
第十一扇门里,它变成了子集和串。
第十二扇门里,它被画成了点和线。
第十三扇门里,它的恒等式被代数证明。
第十四扇门里,它的恒等式被计数证明。
第十五扇门里,它整个数列被压缩成一个分式。
第十六扇门里,它的下标开始说话。
第十七扇门里,它的余数开始循环。
第十八扇门里,循环长度被中国剩余定理拆开。
第十九扇门里,它反过来表示任意整数。
第二十扇门里,它跑到抛硬币里。
第二十一扇门里,它推广到 kk-bonacci。

门开了 21 扇。数列没变。

变的是我们看它的方式。

数学从来不只是一种写法。同一个对象,可以被无数种语言描述。每一种语言都看见一种结构,每一种结构都揭示一种性质。学数学的乐趣,一大半就在这种"换一种说法,看见一种新东西"的瞬间里。

斐波那契数列是这个乐趣的最便宜入口。它不需要任何前置知识,只需要加法。但它能撑起一整本关于"数学如何换语言"的书。

下一串你想去看的数是什么?

也许你会从某个看起来更简单的对象开始,比如素数、平方数、Catalan 数、二项式系数。也许你会从某个看起来更复杂的对象开始,比如 Riemann zeta 函数、模形式、随机矩阵。

无论从哪里开始,记住一件事:

别只用一种语言看它。

试试用代数看它。用算法看它。用矩阵看它。用组合看它。用图论看它。用数论看它。用模运算看它。用概率看它。用几何看它。用拓扑看它。用任何你会的语言看它。

每种语言都会让你看见一点别的语言看不见的东西。

这就是数学。

这就是我们为什么花 21 章只看一串数。

谢谢读到这里的你。