矩阵快速幂

矩阵快速幂,就是矩阵乘法和快速幂的结合,所以在讲矩阵快速幂之前,先讲讲矩阵乘法和快速幂。

矩阵

矩阵的定义

什么是矩阵?由 m×n 个数 ai,j(i=1,2,,m;j=1,2,,n) 排成的一个 mn 列的数表

a1,1a1,2a1,na2,1a2,2a2,nam,1am,2am,n

被称为 mn 列矩阵,简称 m×n 矩阵。为了表示这是个整体总是加一个括弧,并用大写字母表示他,计做:

A=[a1,1a1,2a1,na2,1a2,2a2,nam,1am,2am,n]

这个矩阵中的m×n个数被称为矩阵A的元素,简称元。矩阵A也可被计做Am×n

行矩阵(行向量): 只有一行的矩阵。
列矩阵(列向量): 只有一列的矩阵。
同型矩阵: 两个矩阵AB的行数和列数都相等
单位矩阵: 在矩阵乘法中起着很大的作用,相当于乘法中的1,她是个方阵(即行数和列数相同的矩阵),左上角到右下角的对角线(被称为主对角线)上的元素都是1,除此之外都是0。如下就是一个4×4的单位矩阵:

[1000010000100001]

矩阵加法

我们定义两个m ×n的矩阵AB相加为:

A+B=[a1,1+b1,1a1,2+b1,2a1,n+b1,na2,1+b2,1a2,2+b2,2a2,n+b2,nam,1+bm,1am,2+bm,2am,n+bm,n]

注意:

  • 矩阵加法满足交换律和结合律
  • 只有两个同型矩阵才可以做加法

矩阵乘法

我们定义两个矩阵 Am×sBs×n 相乘得到一个矩阵 Cm,n,并且

ci,j=ai,1b1,j+ai,2b2,j+ai,sbs,j=k=1sai,kbk,j

并将此计做 C=AB
注意: 矩阵乘法不满足交换律,但满足结合律和分配律。
这里没放图是因为放了也看不懂(逃
如果还不理解的话可以看这里
用代码写出来就是下面这个亚子:

struct matrix
{
	int n,m;//n是行数,m是列数
	ll e[105][105];
}a;
matrix mul(matrix a,matrix b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=b.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			ans.e[i][j]=0;//初始化
	for(int i=1;i<=a.n;i++)
		for(int j=1;j<=b.m;j++)
			for(int k=1;k<=a.m;k++)
				ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;//矩阵乘法结果可能很大,所以要取模
	return ans;
}

快速幂

一般我们求 xy 的方法是一个个乘过去,但这种方法太慢了。我们需要一种新的,快速的方法来求,而这种方法就是快速幂。这里我们举例来理解,就比如求 35,用 ans 记录答案。5 转成二进制就是 (101)2,那么我们从右往左开始枚举,首先,有个1,那么我们就记录 ans=ans×3=1×3=3,然后让 3 变成自己的平方,也就是 32=9 ,然后接下来是个 0,那就不用乘,然后再让 9 变成自己的平方 81,然后又是个 1,所以 ans=ans×81=3×81=243。最后,得到 35=243。手算一下可以发现这是对的。当然 xy 很可能是个很大的数,所以一般都会取模。
代码实现长这个亚子:

long long power(long a,long b,long c)
{
	long long ans=1;
	while(b)//没有枚举完
	{
		if(b&1) ans=(ans*a)%c;//这一位是否是1
		a=(a*a)%c;
		b>>=1;//走到下一位
	}
	return ans%c;
}

矩阵快速幂

讲完了上面这些,那矩阵快速幂就很好理解了!就是把运算改成矩阵运算。
首先,ans 和返回值都应该是一个矩阵;然后,ans 应该初始化为单位矩阵,原因上面已经讲了;(1.1 矩阵的定义)再然后,里面的乘法应该改成矩阵乘法。最后提醒一句,矩阵快速幂必须是个方阵,否则要两个同型矩阵(自己乘自己,当然是同型矩阵啦~)可以实现矩阵乘法是不可能的。
代码实现:

struct matrix
{
	int n,m;
	ll e[105][105];
}a;
matrix mul(matrix a,matrix b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=b.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			ans.e[i][j]=0;
	for(int i=1;i<=a.n;i++)
		for(int j=1;j<=b.m;j++)
			for(int k=1;k<=a.m;k++)
				ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;//快速幂的取模就在这里了!
	return ans;
}//代码和上面的区别
matrix power(matrix a,ll b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=a.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			if(i==j) ans.e[i][j]=1;
			else ans.e[i][j]=0;//初始化为单元矩阵
	while(b)
	{
		if(b&1) ans=mul(ans,a);//要把乘换成矩阵乘法哦!
		a=mul(a,a);//这里也一样呢
		b>>=1;
	}//快速幂板子
	return ans;
}

实际应用

先挂上神仙同学的矩阵加速友链

矩阵快速幂可以应用到很多递推题上。
先放出一道例题:洛谷P1962 斐波那契数列

这题看到 n<263 就知道,直接递推肯定不行,我们就要用矩阵快速幂来加加速。
我们知道,这题是由初始的两个数递推得来的,那么我们有没有什么办法让他用更快的速度来递推呢?
当然是用矩阵啦~
由于递推 Fn 需要用到 Fn1,Fn2 两个项,所以我们就构建有两个项的初始矩阵:

A=[Fn1Fn2]

然后,显而易见地,目标矩阵长这个样子:

B=[FnFn1]

然后,就有了一个递推式子:

AC=B

但是我们不知道这个 C 是什么呀。
没关系,我们来把上面的式子拆开来:

AC=[fn1×c1,1+fn2×c2,1fn1×c2,1+fn2×c2,2]

为了让结果等于 B,必须要让

  • fn1×c1,1+fn2×c2,1=fn1+fn2,所以 c1,1,c2,1 都应该等于 1
  • fn1×c1,2+fn2×c2,2=fn1,所以 c1,2=1,c2,2=0

于是就得到了

C=[1110]

但是,这样不还是 O(n) 递推吗?
观察一下,可以发现每次乘的是同一个矩阵,而原来的递推每次的项是不一样的。
这样有什么好处呢?我们可以先让所有的 C 都乘起来,再乘 A 就行了。
能这样做,还是因为矩阵乘法满足结合律
那么要乘几个 C 呢?是 n2 次。为什么呢?因为初始矩阵中已经有了两项:F1F2

最终代码如下:

#include<cstdio>
#define ll long long
#define mod 1000000007
using namespace std;
ll n;
struct matrix
{
	int n,m;
	ll e[105][105];
}a,b;
matrix mul(matrix a,matrix b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=b.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			ans.e[i][j]=0;
	for(int i=1;i<=a.n;i++)
		for(int j=1;j<=b.m;j++)
			for(int k=1;k<=a.m;k++)
				ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;
	return ans;
}
matrix power(matrix a,ll b)
{
	matrix ans;
	ans.n=a.n;
	ans.m=a.m;
	for(int i=1;i<=ans.n;i++)
		for(int j=1;j<=ans.m;j++)
			if(i==j) ans.e[i][j]=1;
			else ans.e[i][j]=0;
	while(b)
	{
		if(b&1) ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}
int main()
{
	scanf("%lld",&n);
	if(n<=2)
	{
		printf("1");
		return 0;
	}
	a.n=2,a.m=2;
	a.e[1][1]=a.e[1][2]=a.e[2][1]=1;
	a=power(a,n-2);
	b.n=1,b.m=2;
	b.e[1][1]=b.e[1][2]=1;
	printf("%lld",mul(b,a).e[1][1]);
	return 0;
}
posted @   Mine_King  阅读(1788)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示