【模板:矩阵加速递推】信息学奥赛一本通 1642:【例 2】Fibonacci 第 n 项

【题目链接】

ybt 1642:【例 2】Fibonacci 第 n 项

【题目考点】

1. 矩阵加速递推

矩阵加速递推步骤:

  1. 根据递推关系确定状态转移矩阵。
  2. 对状态转移矩阵求快速幂。
  3. 求幂后的状态转移矩阵乘以初始状态矩阵,得到最终状态。

设计转移矩阵的核心思路为:递推式右侧的每一项,在等号左侧都要出现其下一项。

该算法的时间复杂度为 O ( d 3 log ⁡ n ) O(d^3\log n) O(d3logn),其中 d d d为方阵的阶, n n n为递推的次数,即转移矩阵的指数。

2. 矩阵快速幂

【解题思路】

设 f i f_i fi是斐波那契数列第 i i i项,斐波那契数数列的递推式为 f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2

如果直接递推求斐波那契数列,时间复杂度为 O ( n ) O(n) O(n),1s时间内只能求出 10 8 10^8 108项。而本题求 f n f_n fn, n n n最大达到 10 9 10^9 109,因此普通递推无法解决本题。

如果将状态转移方程写成状态转移矩阵,通过矩阵运算的方式完成状态转移,则可以通过快速幂来加速递推过程。
f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2写成矩阵(向量)乘法形式为:
f i = [ 1 1 ] [ f i − 1 f i − 2 ] f_i=\begin{bmatrix} 1 & 1\end{bmatrix} \begin{bmatrix} f_{i-1}\\f_{i-2}\end{bmatrix} fi=[11][fi−1fi−2]

同理
f i + 1 = [ 1 1 ] [ f i f i − 1 ] f_{i+1}=\begin{bmatrix} 1 & 1\end{bmatrix} \begin{bmatrix} f_{i}\\f_{i-1}\end{bmatrix} fi+1=[11][fifi−1]

要想得到 f i + 1 f_{i+1} fi+1,需要递推求出 [ f i f i − 1 ] \begin{bmatrix} f_{i}\\f_{i-1}\end{bmatrix} [fifi−1]

而这一项应该通过已知的 [ f i − 1 f i − 2 ] \begin{bmatrix} f_{i-1}\\f_{i-2}\end{bmatrix} [fi−1fi−2]推出。

假设存在一个转移矩阵 [ a 11 a 12 a 21 a 22 ] \begin{bmatrix} a_{11} & a_{12}\\ a_{21} & a_{22}\end{bmatrix} [a11a21a12a22]满足

f i f i − 1 \] = \[ a 11 a 12 a 21 a 22 \] \[ f i − 1 f i − 2 \] \\begin{bmatrix} f_{i}\\\\f_{i-1}\\end{bmatrix}=\\begin{bmatrix} a_{11} \& a_{12}\\\\ a_{21} \& a_{22}\\end{bmatrix}\\begin{bmatrix} f_{i-1}\\\\f_{i-2}\\end{bmatrix} \[fifi−1\]=\[a11a21a12a22\]\[fi−1fi−2

根据矩阵乘法原理,得:
f i = a 11 f i − 1 + a 12 f i − 2 f_i=a_{11}f_{i-1}+a_{12}f_{i-2} fi=a11fi−1+a12fi−2
f i − 1 = a 21 f i − 1 + a 22 f i − 2 f_{i-1}=a_{21}f_{i-1}+a_{22}f_{i-2} fi−1=a21fi−1+a22fi−2

易知:
f i = 1 ⋅ f i − 1 + 1 ⋅ f i − 2 f_i=1\cdot f_{i-1}+1\cdot f_{i-2} fi=1⋅fi−1+1⋅fi−2
f i − 1 = 1 ⋅ f i − 1 + 0 ⋅ f i − 2 f_{i-1}=1\cdot f_{i-1}+0\cdot f_{i-2} fi−1=1⋅fi−1+0⋅fi−2

因此转移矩阵为 [ 1 1 1 0 ] \begin{bmatrix} 1 & 1\\ 1 & 0\end{bmatrix} [1110]

或者根据递推式右侧的每一项,在等号左侧都要出现其下一项

已知: f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi−1+fi−2
f i − 1 f_{i-1} fi−1的下一项是 f i f_i fi,在等号左侧已经出现。
f i − 2 f_{i-2} fi−2的下一项是 f i − 1 f_{i-1} fi−1,需要在等号左侧出现,即需要写出 f i − 1 f_{i-1} fi−1等于什么。已有的等号有边的量存在 f i − 1 f_{i-1} fi−1,自然就可以写为 f i − 1 = f i − 1 f_{i-1}=f_{i-1} fi−1=fi−1,这就是第二个递推式,二者可以整合为上述矩阵形式。

矩阵形式的状态转移方程为:

f i f i − 1 \] = \[ 1 1 1 0 \] \[ f i − 1 f i − 2 \] \\begin{bmatrix} f_{i}\\\\f_{i-1}\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\\begin{bmatrix} f_{i-1}\\\\f_{i-2}\\end{bmatrix} \[fifi−1\]=\[1110\]\[fi−1fi−2

因此

f n f n − 1 \] = \[ 1 1 1 0 \] \[ f n − 1 f n − 2 \] = \[ 1 1 1 0 \] 2 \[ f n − 2 f n − 3 \] = . . . = \[ 1 1 1 0 \] n − 2 \[ f 2 f 1 \] \\begin{bmatrix} f_{n}\\\\f_{n-1}\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\\begin{bmatrix} f_{n-1}\\\\f_{n-2}\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\^2\\begin{bmatrix} f_{n-2}\\\\f_{n-3}\\end{bmatrix}=...=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\^{n-2}\\begin{bmatrix} f_{2}\\\\f_{1}\\end{bmatrix} \[fnfn−1\]=\[1110\]\[fn−1fn−2\]=\[1110\]2\[fn−2fn−3\]=...=\[1110\]n−2\[f2f1

其中:

实际实现时,为了实现方便, [ f n f n − 1 ] \begin{bmatrix} f_{n}\\f_{n-1}\end{bmatrix} [fnfn−1]和 [ f 2 f 1 ] \begin{bmatrix} f_{2}\\f_{1}\end{bmatrix} [f2f1]都使用方阵表示,即:

f n 0 f n − 1 0 \] = \[ 1 1 1 0 \] n − 2 \[ f 2 0 f 1 0 \] \\begin{bmatrix} f_{n}\&0\\\\f_{n-1}\&0\\end{bmatrix}=\\begin{bmatrix} 1 \& 1\\\\ 1 \& 0\\end{bmatrix}\^{n-2}\\begin{bmatrix} f_{2}\&0\\\\f_{1}\&0\\end{bmatrix} \[fnfn−100\]=\[1110\]n−2\[f2f100

  • 如果求 f 1 、 f 2 f_1、f_2 f1、f2,需要进行特判,直接输出 f 1 f_1 f1的值 1 1 1或 f 2 f_2 f2的值1 。
  • 如果 n > 2 n>2 n>2,求 f n f_n fn,则使用矩阵快速幂求出 [ 1 1 1 0 ] n − 2 \begin{bmatrix} 1 & 1\\ 1 & 0\end{bmatrix}^{n-2} [1110]n−2,而后使用上述矩阵递推关系求出 f n f_n fn。

该算法的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

【题解代码】

解法1:矩阵加速
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, m;
struct Mat
{
	LL n, a[3][3] = {};
	Mat(){}
	Mat(int _n, int val = 0):n(_n)
	{
		for(int i = 1; i <= n; ++i)
			a[i][i] = val;
	}
	LL* operator [] (int i)
	{
		return a[i];
	}
	Mat operator * (Mat b)
	{
		Mat r(n);
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				for(int k = 1; k <= n; ++k)
					r[i][j] = (r[i][j]+a[i][k]*b[k][j])%m;
		return r;
	}
};
Mat fastPow(Mat a, LL b)//矩阵快速幂
{
	Mat r(2, 1);
	while(b > 0)
	{
		if(b%2 == 1)
			r = r*a;
		a = a*a;
		b /= 2;
	}
	return r;
}
int main()
{
	cin >> n >> m;
	if(n <= 2)
	{
		cout << 1%m;
		return 0;
	}
	Mat t(2), f(2);//t:转移矩阵 f:初始矩阵
	t.a[1][1] = 1, t.a[1][2] = 1, t.a[2][1] = 1;
	f.a[1][1] = 1, f.a[2][1] = 1;
	Mat r = fastPow(t, n-2)*f;//r:结果矩阵
	cout << r.a[1][1];
	return 0;
}
相关推荐
zaiyang遇见4 小时前
【基础排序】USACO Bronze 2016 January - Angry Cows
排序算法·模拟·信息学奥赛·程序设计竞赛·函数封装·usaco
会周易的程序员4 小时前
多模态AI 基于工业级编译技术的PLC数据结构解析与映射工具
数据结构·c++·人工智能·单例模式·信息可视化·架构
lixzest6 小时前
C++上位机软件开发入门深度学习
开发语言·c++·深度学习
苦藤新鸡8 小时前
4.移动零
c++·算法·力扣
hetao17338378 小时前
2026-01-04~06 hetao1733837 的刷题笔记
c++·笔记·算法
liulilittle9 小时前
XDP VNP虚拟以太网关(章节:一)
linux·服务器·开发语言·网络·c++·通信·xdp
Ralph_Y9 小时前
多重继承与虚继承
开发语言·c++
bkspiderx9 小时前
C++虚析构函数:多态场景下的资源安全保障
c++·析构函数·虚函数表·虚析构函数
White_Can9 小时前
《C++11:列表初始化》
c语言·开发语言·c++·vscode·stl
White_Can10 小时前
《C++11:右值引用与移动语义》
开发语言·c++·stl·c++11