斐波那契数列 学习笔记
#1 定义
斐波那契数列
斐波那契数列(The Fibonacci sequence,OEIS A000045)的定义如下:
该数列的前几项如下:
* 卢卡斯数列
卢卡斯数列(The Lucas sequence,OEIS A000032)的定义如下:
该数列的前几项如下:
NOTE
研究斐波那契数列,很多时候需要借助卢卡斯数列为工具。
#2 斐波那契数列通项公式
第 个斐波那契数可以在 的时间内使用递推公式计算。但我们仍有更快速的方法计算。
2.1 解析解
解析解即公式解。我们有斐波那契数列的通项公式(Binet's Formula
这个公式可以很容易地用归纳法证明,当然也可以通过生成函数的概念推导,或者解一个方程得到。
简化
不难发现,这个公式分子的第二项总是小于 ,并且它以指数级的速度减小。因此我们可以把这个公式写成
这里的中括号表示取离它最近的整数。
这两个公式在计算的时候要求极高的精确度,因此在实践中很少用到。但是请不要忽视!结合模意义下二次剩余和逆元的概念,在 OI 中使用这个公式仍是非常有用的。
2.2 矩阵形式
斐波那契数列的递推可以用矩阵乘法的形式表达:
设 ,我们得到
于是我们可以用矩阵乘法在 的时间内计算斐波那契数列。此外,前一节讲述的公式也可通过矩阵对角化的技巧来得到。
快速倍增法
使用上面的方法我们可以得到以下等式:
于是可以通过这样的方法快速计算两个相邻的斐波那契数(常数比矩乘小
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:对于素数 , 是斐波那契数模 的周期,等价于 是斐波那契数模 的周期。特别地, 是模 的皮萨诺周期,等价于 是模 的皮萨诺周期。
证明:
这里的证明需要把 看作一个整体。
由于:
因此:
因为反方向也可以推导,所以 是斐波那契数模 的周期,等价于:
当 是奇素数时,由 升幂引理,有:
当 时,由 升幂引理,有:
代入 为 和 , 为 和 ,上述条件也就等价于:
因此也等价于 是斐波那契数模 的周期。
因为周期等价,所以最小正周期也等价。
三个结论证完。据此可以写出代码:
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;
}