Skip to content

斐波那契数列 学习笔记

#1 定义

斐波那契数列

斐波那契数列(The Fibonacci sequence,OEIS A000045)的定义如下:

该数列的前几项如下:

* 卢卡斯数列

卢卡斯数列(The Lucas sequence,OEIS A000032)的定义如下:

该数列的前几项如下:

NOTE

研究斐波那契数列,很多时候需要借助卢卡斯数列为工具。

#2 斐波那契数列通项公式

个斐波那契数可以在 的时间内使用递推公式计算。但我们仍有更快速的方法计算。

2.1 解析解

解析解即公式解。我们有斐波那契数列的通项公式(Binet's Formula

这个公式可以很容易地用归纳法证明,当然也可以通过生成函数的概念推导,或者解一个方程得到。

简化

不难发现,这个公式分子的第二项总是小于 ,并且它以指数级的速度减小。因此我们可以把这个公式写成

这里的中括号表示取离它最近的整数

这两个公式在计算的时候要求极高的精确度,因此在实践中很少用到。但是请不要忽视!结合模意义二次剩余逆元的概念,在 OI 中使用这个公式仍是非常有用的。

2.2 矩阵形式

斐波那契数列的递推可以用矩阵乘法的形式表达:

,我们得到

于是我们可以用矩阵乘法在 的时间内计算斐波那契数列。此外,前一节讲述的公式也可通过矩阵对角化的技巧来得到。

快速倍增法

使用上面的方法我们可以得到以下等式:

于是可以通过这样的方法快速计算两个相邻的斐波那契数(常数比矩乘小代码如下,返回值是一个二元组

cpp
pair<int, int> fib(int n) {
    if (n == 0)
        return {0, 1};
    auto p = fib(n >> 1);
    int c = p.first * (2 * p.second - p.first);
    int d = p.first * p.first + p.second * p.second;
    if (n & 1)
        return {d, c + d};
    else
        return {c, d};
}

#3 模意义下周期性

考虑模 意义下的斐波那契数列,可以容易地使用抽屉原理证明,该数列是有周期性的。考虑模意义下前 个斐波那契数对(两个相邻数配对

的剩余系大小为 ,意味着在前 个数对中必有两个相同的数对,于是这两个数对可以往后生成相同的斐波那契数列,那么他们就是周期性的。

3.1 皮萨诺周期

意义下斐波那契数列的最小正周期被称为 皮萨诺周期(Pisano periods,OEIS A001175

皮萨诺周期总是不超过 ,且只有在满足 的形式时才取到等号。

当需要计算第 项斐波那契数模 的值的时候,如果 非常大,就需要计算斐波那契数模 的周期。当然,只需要计算周期,不一定是最小正周期。

容易验证,斐波那契数模 的最小正周期是 ,模 的最小正周期是

显然,如果 互素, 的皮萨诺周期就是 的皮萨诺周期与 的皮萨诺周期的最小公倍数。


计算周期还需要以下结论:

结论 1:对于奇素数 是斐波那契数模 的周期。即,奇素数 的皮萨诺周期整除

证明:

此时

由二项式展开:

因为 两项都同余于 ,与 一致,所以 是周期。

结论 2:对于奇素数 是斐波那契数模 的周期。即,奇素数 的皮萨诺周期整除

证明:

此时

由二项式展开:

之后,在 式中,只有 项留了下来;在 式中,有 ,三项留了下来。

于是 两项与 一致,所以 是周期。

结论 3:对于素数 是斐波那契数模 的周期,等价于 是斐波那契数模 的周期。特别地, 是模 的皮萨诺周期,等价于 是模 的皮萨诺周期。

证明:

这里的证明需要把 看作一个整体。

由于:

因此:

因为反方向也可以推导,所以 是斐波那契数模 的周期,等价于:

是奇素数时,由 升幂引理,有:

时,由 升幂引理,有:

代入 ,上述条件也就等价于:

因此也等价于 是斐波那契数模 的周期。

因为周期等价,所以最小正周期也等价。

三个结论证完。据此可以写出代码:

cpp
struct prime {
    unsigned long long p;
    int times;
};

struct prime pp[2048];
int pptop;

unsigned long long get_cycle_from_mod(
    unsigned long long mod) // 这里求解的只是周期,不一定是最小正周期
{
    pptop = 0;
    srand(time(nullptr));
    while (n != 1) {
        __int128_t factor = (__int128_t)10000000000 * 10000000000;
        min_factor(mod, &factor); // 计算最小素因数
        struct prime temp;
        temp.p = factor;
        for (temp.times = 0; mod % factor == 0; temp.times++) {
            mod /= factor;
        }
        pp[pptop] = temp;
        pptop++;
    }
    unsigned long long m = 1;
    for (int i = 0; i < pptop; ++i) {
        int g;
        if (pp[i].p == 2) {
            g = 3;
        } else if (pp[i].p == 5) {
            g = 20;
        } else if (pp[i].p % 5 == 1 || pp[i].p % 5 == 4) {
            g = pp[i].p - 1;
        } else {
            g = (pp[i].p + 1) << 1;
        }
        m = lcm(m, g * qpow(pp[i].p, pp[i].times - 1));
    }
    return m;
}