欢迎这位怪蜀黍来到《矩阵乘法与斐波那契数列 - 童话镇里的星河 - 博客园》

矩阵乘法与斐波那契数列

前言

这篇文章属于矩阵乘法的提高篇,虽然会对基础知识进行讲解,不过建议先进行学习后再来阅读。
不保证能对您的水平带来多大的提高,但一般来说会有的。

正文:

ps:以下文章小写字母及希腊字母代表一个实数,大写字母代表矩阵,fi代表斐波那契数列的第i项。

Part.1 矩阵运算

1.加减法
C=A±B,则:

Ci,j=Ai,j±Bi,j

代码实现:

struct jz
{
	long long a[105][105];
};
//两个n*n的矩阵
jz add(jz x,jz y,int n)
{
	jz c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=0;	
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=x.a[i][j]+y.a[i][j];
	}
	return c;
} 

2.数乘
C=kA,则:

Ci,j=kAi,j

代码实现:

struct jz
{
	long long a[105][105];
};
//一个n*n的矩阵
jz mathmul(jz x,long long k,int n)
{
	jz c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=0;	
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=x.a[i][j]*k;
	}
	return c;
} 

3.乘法
C=A×B,则:

Ci,j=k=1nAi,k×Bk,j

(这里的n代表A的行数或B的列数,当这两个值不同时,相乘无意义)
代码实现:

struct jz
{
	long long a[105][105];
};
//两个n*n的矩阵
jz mul(jz x,jz y,int n)
{
	jz c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=0;	
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{
				c.a[i][j]=c.a[i][j]%MOD+x.a[i][k]*y.a[k][j]%MOD;
			}
		}
	}
	return c;
}

4.求逆
不会,先咕着。

Part.2 矩阵快速幂

我们用自己定义的乘法跑快速幂即可。
例题题目链接:P3390 【模板】矩阵快速幂
代码实现:

#include<iostream>
#include<cstdio>
using namespace std;
struct jz
{
	long a[105][105];
};
jz o;
const int MOD=1e9 +7;
int n;
jz yu;
long long  ci;
jz mul(jz x,jz y)
{
	jz c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=0;	
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{
				c.a[i][j]=c.a[i][j]%MOD+x.a[i][k]*y.a[k][j]%MOD;
			}
		}
	}
	return c;
}
jz quickpow(jz cc,long long k)
{
	jz ans=o,base=cc;
	while(k)
	{
		if(k&1) ans=mul(ans,base);
		base=mul(base,base);
		k>>=1;
	}
	return ans;
}
inline int read()
{
	int x=0,w=1;
	char c=getchar();
	while(c>'9'||c<'0')
	{
		if(c=='-')w=-1;
		c=getchar();
	}
	while(c<='9'&&c>='0')
	{
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	return x*w;
}
int main()
{
   scanf("%d%lld",&n,&ci);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) yu.a[i][j]=read();
	}
	for(int i=1;i<=n;i++) o.a[i][i]=1;
	//这里的o是一个单位矩阵,大家可以想想为什么是这样的
	jz d=quickpow(yu,ci);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) 
		{
		    printf("%lld ",d.a[i][j]%MOD);
 		} 		
		printf("\n");
	}
	return 0;
} 

记得取模即可。

Part.3 疑惑

你可能会问:这是什么破算法,他能干啥?
刚学的我何尝没有这样的疑问,不过现在了解之后,不会让你们再有疑问了。
矩阵乘法是一种巧妙地方式将加法转化成乘法的方式,以便在较短的方式解决递推问题。
如果还是不太清楚,那我们就看些例子吧。

Part.4 简单好啃的栗子

洛谷是有板子的:
题目链接:P1939 【模板】矩阵加速(数列)
对于这种加法形递推式,一般都可以使用矩阵乘法加速递推。
我们做矩阵乘法的原理是用一个矩阵封装有用的变量,使用乘法实现多元加法,得到这个参数的下一项。
我们开始考虑要维护什么,显然是ai辣。
那我们考虑如何由ai1得到ai.
那我们先写一个不全的递推式:

ai1ai

然后尝试在左边加上某个(些)东西使等号成立。
很简单:

ai1+ai3=ai

显然要考虑维护ai3,并且记得去维护下一个下标的ai3,显然是ai2
接着考虑维护ai2的下一个是ai1,这个我们已经保存了,那么就好了。
我们尝试用找到的规律画一张表,标出相应的系数:

ai1 ai2 ai3
ai1ai 1 0 1
ai2ai1 1 0 0
ai3ai2 0 1 0

这是我们构造和表格一样的矩阵A

(101100010)

和:

(ai1ai2ai3)

模拟下矩阵乘法,就会发现这就是我们所有想要的运算。
我们考虑从i=3开始,递推k2次,即把A进行k2次幂。
然后乘上基础的矩阵得到的值即可得到答案。
为防止文章过长,代码自己写吧,我就不给了。

Part.5 活学活用

再给你一道题:
题目链接:P1962 斐波那契数列
构造转移矩阵:

(1110)

太简单了,再来一道:
题目链接;P1349 广义斐波那契数列
转移矩阵:

(pq10)

初始矩阵:

(a2a1)

直接做就好了。

Part.6 尝试搞事情

其实斐波那契数列有很多强势的做法,这里只叙述两种:
1.我自己yy的。
比较久远,大家可以去我的博客查看。-->快戳这儿,戳这儿
这里给出关键的式子,并命名为“斐波那契数列性质1”;

fn=fafna+1+fa1fna

可以用斐波那契数列定义(递推式)去证明,不再赘述了。
2.较为普及的算法。
我们称之为“斐波那契数列性质2”:

{f2n=fn+12fn12f2n+1=fn2+fn+12

第一个式子可以化简:

fn+12fn12=(fn+1+fn1)(fn+1fn1)=(fn+fn1+fn1)fn=(fn+2fn1)fn

数学归纳法证明。
首先验证0<n<5时成立。
设第2n项之前的所有项都成立(不包含)。
那么:

f2n=f2n2+f2n1=fn2+fn2+fn2+fn12=fn(fn3+2fn)=fn(fn3+fn+fn1+fn2)=fn(fn+2fn1)

得证。
另一种情况:

f2n+1=f2n+f2n1=fn+12fn12+fn2+fn12=fn2+fn+12

得证。
其实很简单的了,大头在后面。

Part.7 斐波那契数列的通项公式

众所周知:

fn=15((1+52)n(152)n)

根据二项式定理展开可以得到(称为斐波那契数列性质3):

fn=i=1nCni×5n12×(i%2)2n1

暴力展开即可证明,不再赘述。
我们要用两种方式证明通向公式:

方法一:生成函数法(不懂可以跳过):

设斐波那契数列的生成函数为G(x),即:

G(x)=i1fixi

然后套路的减一下:

G(x)xG(x)=x+x2G(x)

ps:上式靠手玩。

G(x)=x1xx2

因式分解:

G(x)=x(1152x)(11+52x)

博主只会暴算和乱凑,谁有好方法鸭?
然后裂项:

G(x)=151(1152x)+151(11+52x)

我再bb一下裂项吧:
对于上例,可以设a,b,令:

a1(1152x)+b1(11+52x)=G(x)

暴力解得a,b
分治前面这一项,并把系数15提出来,要处理的就是这么一个玩意:

1(1152x)

考虑到生成函数是等比数列求和的形式,把公比设成q,那么:

f1(1qn)1q=1(1152x)

把已知与极限代入可得:

11q=1(1152x)

解得:

q=152

后面那些同理即得:

fn=15((1+52)n(152)n)

这啥玩意,还这么难算?

方法二:特征根法:

首先明确所有线性递推数列(学名“线性常系数齐次递推”)都是等比数列。
我们有fn=fn1+fn2,那么有公比a满足fn=an,所有的a即为递推式的特征根。
显然:

an=an1+an2

同时除以an2,得:

a2a1=0

a1=1+52,a2=152

那么有

fn=xa1n+ya2n

f0=0,f1=1,分别带入,得:

x=55,y=55

同样得到通项公式。

Part.8 斐波那契数列的几何意义

给一张图,奥妙无穷。

Part.9 稍有思维难度的矩阵题

上面扯出矩阵了,我们再扯回来。
注意:
1.下面的题都是口胡题解,如果有锅请积极提出,另外没有代码。
2.我会“布置”一些作业,如果你觉得太屑了,就不做了吧。
3.下面所有运算都在mod一个数的意义下,我就不写了。
4.数据范围:n1018

T1.

i=1nfi
做法一:
构造:

(i=1n1fifn1fn2)×(111011010)

做法二:
利用斐波那契数列性质4

i=1nfi=fn+21

证明:

i=1nfi=f3+i=3nfi=f3+f5+i=5nfi=......

分类讨论:
n为奇数时:

i=1nfi=i=12i+1nf2i+1+fn

假设相等,那么:

i=12i+1nf2i+1+fn=fn+21

i=12i+1nf2i+1+fn=fn+fn+11

i=12i+1nf2i+1=fn+11

i=12i+1nf2i+1=fn+fn11

我们会发现无限拆下去,出现了:

f3=f41

的情况,并且这是成立的。
对于n为偶数的情况,也可以用同样的方法验证结论是成立的。
其实上面的证明都是吃多了的做法!
笔者在散步时想出了简单易懂的证明方法。
考虑数学归纳法。
我们验证n=1时成立。
那我们假设n及以前都成立:

i=1nfi=fn+21

那么:

i=1n+1fi=fn+2+fn+11=fn+31

得证。
然后用矩阵乘法求出fn+2就好了。
作业1:用几何法证明利用斐波那契数列性质4

T2.

i=1nfi2
有难度,但是可以尝试:

i=1n1fi2i=1nfi2

显然需要一个fi2
考虑:

fn12fn2

由于:

fn2=(fn1+fn2)2=fn12+fn22+2fn1fn2

那么考虑维护fn22fn1fn2
由于fn22fn12可以直接继承,不用管了。
又因为:

fnfn1=fn1(fn1+fn2)=fn12+fn1fn2

都是可以继承的,那么我们构造矩阵:

(i=1n1fi2fn12fn22fn1fn2)×(1112011201000101)

好难啊!
我们可以证明斐波那契数列性质5来解决问题。
我们设S(n)=i=1nfi2,C(n)=i=3nfi1fi2
我们发现:

C(n)=i=3nfi1fi2=i=3n(fi2+fi3)fi2=S(n2)+C(n1)

两边差分一下得:

S(n2)=fn1fn2

很容易拓展到:

i=1nfi2=fnfn+1

算出fn,fn+1即可。
作业2.用几何法证明斐波那契数列性质5

T3.

i=1n(ni+1)fi
带变化性系数,一眼很不可做。
但是发现他就是这么个东西:

i=1nj=1ifj

根据斐波那契数列性质4:

原式=i=3n+2fin=i=1n+2n2=fn+4n3

利用矩阵直接计算即可。

T4.

n个方块排成一排,共有红、蓝、黄、绿四种颜色。求红色与绿色方块个数为偶数的方案数(不考虑顺序,只考虑各种颜色的个数)。
一道好题。
方法一:
设当有i个方块时,记红绿都为偶数的方案数为ai,一个为偶数的方案数为bi,都不是的方案数为ci
容易得到:

{ai=2ai1+bi1bi=2ai1+2bi1+2ci1ci=bi1+2ci1

那么构造:

(ai1bi1ci1)×(210222012)

方法二:
学过生成函数的同学都知道这是指数生成函数的入门题。
容易得到:

g(e)=(1+x22!+x44!+......)2(1+x1!+x22!+......)2=(12(ex+ex))2×e2x=14(e4x+2e2x+1)

容易得到通项公式:

hi=4i+2i+14i>0

emmm...直接快速幂就好了。

Part.10 另一类模型

题目链接:CF1182E Product Oriented Recurrence
带上乘法就很难搞了,不过这是一种模型
考虑维护系数,我们记(f不再是斐波那契数列):

fi=cwif1xif2yif3zi

容易得到递推式:

{wi=wi1+wi2+wi3+2i4xi=xi1+xi2+xi3yi=yi1+yi2+yi3zi=zi1+zi2+zi3

我们不妨让数列从第4个开始编号,即:

fi=cwi3f1xi3f2yi3f3zi3

然后构造一大堆矩阵,发现x,y,z比较套路,就不说了。
对于wi,构造:

(wi1wi2wi32i2)×(1111010000010000001100001)

我们再运用拓展欧拉定理,来优化系数:不会的戳这里
由于φ(1e9+7)=1e9+6,那我们把系数mod1e9+6就好了。
但我们记得当数大于φ(m)时,要加φ(m),我们打个表判断就好,事实证明超过1e9+6xi,yi,zi,wi对应的数列项数(从一开始编号,即把矩阵数列下标+1.)分别为38,38,37,35,判断一下即可。
要注意定义矩阵后清空,否则会死的很惨(随机赋值我佛了)。
代码实现:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

#define ll long long 
#define read(x) scanf("%lld",&x)
#define MOD 1000000006
#define MODD 1000000007

ll c,f1,f2,f3,rt;
ll ansx,ansy,ansz,answ;
ll ans=1;
struct mat
{	
	ll a[10][10];	
}e;

mat mul(mat x,mat y,int n,int mod)
{
	mat c;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) c.a[i][j]=0;	
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{
				c.a[i][j]=c.a[i][j]%mod+x.a[i][k]*y.a[k][j]%mod;
			}
		}
	}
	return c;
}

mat qp(mat cc,ll k,int n,int mod)
{
	mat ans=e,base=cc;
	while(k)
	{
		if(k&1) ans=mul(ans,base,n,mod);
		base=mul(base,base,n,mod);
		k>>=1;
	}
	return ans;
}

ll quickpow(ll a,ll b,ll mod)
{
	ll ans=1,base=a%mod;
	while(b)
	{
		if(b&1) ans=ans*base%mod;
		b>>=1;
		base=base*base%mod;	
	}	
	return ans%mod;
}

int main()
{
	read(rt),read(f1),read(f2),read(f3),read(c);
	//mod 1000000006;
	for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) e.a[i][j]=0;
	for(int i=1;i<=5;i++) e.a[i][i]=1;
	mat x,xx;
	if(rt<=6) ansx=(rt<=5)?1:2;
	else
	{
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) x.a[i][j]=0;
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) xx.a[i][j]=0;
		x.a[1][1]=x.a[1][2]=x.a[1][3]=x.a[2][1]=x.a[3][2]=1;
		xx.a[1][1]=2,xx.a[2][1]=xx.a[3][1]=1;
		mat op1=qp(x,rt-6,3,MOD);
		op1=mul(op1,xx,3,MOD);
		ansx=op1.a[1][1]%MOD;
	}
	mat y,yy;
	if(rt<=6) ansy=rt-3;
	else 
	{
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) y.a[i][j]=0;
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) yy.a[i][j]=0;
		y.a[1][1]=y.a[1][2]=y.a[1][3]=y.a[2][1]=y.a[3][2]=1;
		yy.a[1][1]=3,yy.a[2][1]=2,yy.a[3][1]=1;
		mat op2=qp(y,rt-6,3,MOD);
		op2=mul(op2,yy,3,MOD);
		ansy=op2.a[1][1]%MOD;
	}
	mat z,zz;
	if(rt<=6) ansz=pow(2,rt-4);
	else
	{
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) z.a[i][j]=0;
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) zz.a[i][j]=0;
		z.a[1][1]=z.a[1][2]=z.a[1][3]=z.a[2][1]=z.a[3][2]=1;
		zz.a[1][1]=4,zz.a[2][1]=2;zz.a[3][1]=1;
		mat op3=qp(z,rt-6,3,MOD);
		op3=mul(op3,zz,3,MOD);
		ansz=op3.a[1][1]%MOD;
	}
	mat w,ww;
	if(rt<=6){if(rt==4) answ=2;else if(rt==5) answ=6;else answ=14;}
	else
	{
		for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) w.a[i][j]=0;
		for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) ww.a[i][j]=0;
		w.a[1][1]=w.a[1][2]=w.a[1][3]=w.a[1][4]=1;
		w.a[2][1]=w.a[3][2]=1;
		w.a[4][4]=w.a[4][5]=w.a[5][5]=1;
		ww.a[1][1]=14,ww.a[2][1]=6,ww.a[3][1]=2,ww.a[4][1]=8,ww.a[5][1]=2;
		mat op4=qp(w,rt-6,5,MOD);
		op4=mul(op4,ww,5,MOD);
		answ=op4.a[1][1]%MOD;
	}
	if(rt>=38) ans=ans*quickpow(f1,ansx+MOD,MODD)%MODD,ans=ans*quickpow(f2,ansy+MOD,MODD)%MODD;
	else ans=ans*quickpow(f1,ansx,MODD)%MODD,ans=ans*quickpow(f2,ansy,MODD)%MODD;
	if(rt>=37) ans=ans*quickpow(f3,ansz+MOD,MODD)%MODD;
	else ans=ans*quickpow(f3,ansz,MODD)%MODD;
	if(rt>=35) ans=ans*quickpow(c,answ+MOD,MODD)%MODD;
	else ans=ans*quickpow(c,answ,MODD)%MODD;
	printf("%lld\n",ans%MODD);
	return 0;
}

这种模型的原理:
利用已知的数列元素,把后面每一项表示出来,这是通常是这个元素幂的积的形式,通常使用拓展欧拉定理优化幂次。
核心思想是:同底数幂相乘,底数不变,指数相加。

拓展:
i=1nfi(这里的fi指上面定义的数列)
我们考虑维护xi,yi,zi,wi
最后把x,y,z总和加上1即可。

Part.11 结语

首先感谢@Miraclys 大佬的审稿。
垃圾博主tlx同学耗费将近8个小时完成了这篇博客的构思+叙述。
他很累,当然他要写的东西还有很多,好在他很快乐于与他人分享学习成果。
可悲的是AFO离他越来越近了。
以后像这么长的文章不知还有没有,喜欢的话就给他点鼓励吧(愣着干啥,点赞啊!)

posted @   童话镇里的星河  阅读(1718)  评论(2编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示