k-斐波那契数列与矩阵快速幂
问题:定义数列 (F):
- 前 (k) 项均为 1:(F_1 = F_2 = \cdots = F_k = 1)
- 第 (n) 项((n > k))为前 (k) 个相邻项之和:(F_n = F_{n-1} + F_{n-2} + \cdots + F_{n-k})
求 (F_n) 的值((n) 和 (k) 可能很大,需要快速计算)。
当 (k=2) 时即为经典斐波那契数列。
一、问题本质:线性递推
递推式是线性、齐次的,只依赖前 (k) 项。这种递推都可以写成「状态向量 × 转移矩阵」的形式,从而用矩阵快速幂把「逐项递推」变成「矩阵的 ((n-k)) 次幂」,将单次求 (F_n) 的时间从 (O(n)) 降到 (O(k^3 \log n))。
二、朴素做法为什么慢
直接按定义递推:
1 | F[1..k] = 1 |
- 时间:(O(n)),(n) 很大时不可接受。
- 空间:可以只保留最近 (k) 个数,(O(k))。
若 (n = 10^9)、(k = 10),显然不能真的循环 (10^9) 次,需要把「递推 (n-k) 步」变成「做 (\log(n-k)) 次矩阵乘法」。
三、状态向量与转移矩阵
3.1 状态向量
递推只依赖「当前连续 (k) 个数」,因此用长度为 (k) 的向量表示「状态」:
[
\mathbf{v}n =
\begin{bmatrix} F_n \ F{n-1} \ F_{n-2} \ \vdots \ F_{n-k+1} \end{bmatrix}
]
也就是说,(\mathbf{v}n) 的第 0 个分量就是 (F_n),后面依次是 (F{n-1},, F_{n-2},, \ldots,, F_{n-k+1})。
3.2 下一步状态
递推一步后:
- 新的「当前项」为:(F_{n+1} = F_n + F_{n-1} + \cdots + F_{n-k+1}),即 (\mathbf{v}_n) 的 (k 个分量之和)。
- 新的「前一项」为 (F_n),即原 (\mathbf{v}_n) 的第 0 个分量。
- 再往前依次是 (F_{n-1},, F_{n-2},, \ldots,, F_{n-k+2}),都来自 (\mathbf{v}_n) 的前 (k-1) 个分量依次下移一位。
因此:
[
\mathbf{v}{n+1} =
\begin{bmatrix} F{n+1} \ F_n \ F_{n-1} \ \vdots \ F_{n-k+2} \end{bmatrix}
\begin{bmatrix}
1 & 1 & 1 & \cdots & 1 \
1 & 0 & 0 & \cdots & 0 \
0 & 1 & 0 & \cdots & 0 \
\vdots & \ddots & \ddots & \ddots & \vdots \
0 & \cdots & 0 & 1 & 0
\end{bmatrix}
\begin{bmatrix} F_n \ F_{n-1} \ F_{n-2} \ \vdots \ F_{n-k+1} \end{bmatrix}
= M \mathbf{v}_n
]
3.3 转移矩阵 (M) 的结构
(M) 是 (k \times k) 矩阵:
- 第 0 行:全 1,表示 (F_{n+1} = F_n + F_{n-1} + \cdots + F_{n-k+1})。
- 第 1 行:([1,0,\ldots,0]),表示新状态的「前一项」= 原 (F_n)。
- 第 2 行:([0,1,0,\ldots,0]),表示再前一项 = 原 (F_{n-1})。
- 以此类推,第 (i) 行((i \ge 1))只有第 (i-1) 列为 1,其余为 0。
即:
[
M =
\begin{bmatrix}
1 & 1 & 1 & \cdots & 1 \
1 & 0 & 0 & \cdots & 0 \
0 & 1 & 0 & \cdots & 0 \
\vdots & \ddots & \ddots & \ddots & \vdots \
0 & \cdots & 0 & 1 & 0
\end{bmatrix}
]
于是:
[
\mathbf{v}n = M \mathbf{v}{n-1} = M^2 \mathbf{v}_{n-2} = \cdots = M^{n-k} \mathbf{v}_k
]
3.4 初始向量 (\mathbf{v}_k)
当 (n = k) 时,状态为 ([F_k,, F_{k-1},, \ldots,, F_1]^T),由定义 (F_1 = \cdots = F_k = 1),故:
[
\mathbf{v}_k = [1,, 1,, \ldots,, 1]^T
]
因此:
[
\mathbf{v}_n = M^{n-k} \mathbf{v}_k
]
(F_n) 即为 (\mathbf{v}_n) 的第 0 个分量,也就是 (M^{n-k}) 的第 0 行与 (\mathbf{v}_k) 的内积。由于 (\mathbf{v}_k) 全为 1,即:
[
F_n = \sum_{j=0}^{k-1} (M^{n-k})_{0,j}
]
也就是说:先算 (M^{n-k}),再取第 0 行所有元素之和即可得到 (F_n)。
四、矩阵快速幂
要算 (M^{n-k}),用快速幂思想:
- (M^0 = I)(单位矩阵)
- (M^{2t} = (M^t)^2)
- (M^{2t+1} = M \cdot M^{2t})
每次乘法是 (k \times k) 矩阵乘 (k \times k),单次 (O(k^3));指数 (n-k) 的二进制长度为 (O(\log n)),故乘法次数 (O(\log n))。
总时间复杂度:(O(k^3 \log n))。
若要对 (10^9+7) 取模,在矩阵运算中每一步对模数取模即可。
五、算法流程小结
- 若 (n \le k):直接返回 1。
- 若 (n > k):
- 构造 (k \times k) 转移矩阵 (M)(第 0 行全 1,第 (i) 行仅第 (i-1) 列为 1,(i \ge 1))。
- 计算 (P = M^{n-k})(矩阵快速幂)。
- (F_n = \sum_{j=0}^{k-1} P_{0,j})(对模数取模)。
六、代码实现(Python,含取模)
1 | MOD = 10**9 + 7 |
七、小例子:k=2(普通斐波那契)
状态向量为 (\mathbf{v}n = [F_n,, F{n-1}]^T),转移矩阵:
[
M = \begin{bmatrix} 1 & 1 \ 1 & 0 \end{bmatrix}
]
初始 (\mathbf{v}_2 = [1,, 1]^T)。
例如求 (F_5):(\mathbf{v}_5 = M^{5-2} \mathbf{v}_2 = M^3 [1,,1]^T)。
[
M^3 = M \cdot M^2 = \begin{bmatrix} 1 & 1 \ 1 & 0 \end{bmatrix}
\begin{bmatrix} 2 & 1 \ 1 & 1 \end{bmatrix}
= \begin{bmatrix} 3 & 2 \ 2 & 1 \end{bmatrix}
]
(F_5 = 3 \cdot 1 + 2 \cdot 1 = 5),与定义一致。
八、推广:任意线性递推
凡是形如:
[
a_n = c_1 a_{n-1} + c_2 a_{n-2} + \cdots + c_k a_{n-k}
]
的线性齐次递推,都可以写成 (\mathbf{v}n = M \mathbf{v}{n-1}),其中 (\mathbf{v}n = [a_n,, a{n-1},, \ldots,, a_{n-k+1}]^T),(M) 的第一行是 ([c_1,, c_2,, \ldots,, c_k]),其余行是「单位下移」(第 (i) 行第 (i-1) 列为 1)。再用矩阵快速幂即可 (O(k^3 \log n)) 求 (a_n)。k-斐波那契只是 (c_1 = c_2 = \cdots = c_k = 1) 的特例。
九、复杂度与注意点
| 项目 | 复杂度 / 说明 |
|---|---|
| 单次求 (F_n) | 时间 (O(k^3 \log n)),空间 (O(k^2)) |
| 取模 | 矩阵运算每步对 MOD 取模,避免溢出 |
| 大数 (n) | (\log n) 次矩阵乘法,适合 (n) 达 (10^9) 级别 |
| (k) 较大 | (k^3) 占主导,若 (k) 上百可考虑循环节、生成函数等其它方法 |
小结:k-斐波那契是「前 k 项为 1、每项为前 k 项和」的线性递推。用状态向量 (+) 转移矩阵写成 (\mathbf{v}_n = M^{n-k} \mathbf{v}_k),再对 (M) 做快速幂,即可在 (O(k^3 \log n)) 内求 (F_n)。同一套思路适用于所有线性齐次递推。