矩阵乘法 与 矩阵快速幂详解 以51NOD1242 斐波那契数列的第N项为例

首先介绍矩阵乘法的本质:

小明今天要做饭,消耗2斤肉,1斤蔬菜。肉每斤20元,蔬菜每斤5元,则一共需多少花费?
这个问题的答案很简单:

我们用向量相乘的方法写出来:

如果小明第二天有另一种做饭的方法,需要消耗1斤肉,4斤蔬菜,那么这两种方法的花费各是多少呢?我们显然需要另算这第二种方法的花费。把这个做饭方式写在第二个矩阵(向量是宽度或长度为1的矩阵)里:
小明家附近还有另一个菜市场,那里肉每斤15元,蔬菜每斤10元。那么,小明如果去这个菜市场,花费又是多少呢(分别计算上述两种做饭方式)?我们把这另外的一种价格写进第一个矩阵里:

小明家附近还有另一个菜市场,那里肉每斤15元,蔬菜每斤10元。那么,小明如果去这个菜市场,花费又是多少呢(分别计算上述两种做饭方式)?我们把这另外的一种价格写进第一个矩阵里:


这样我们看到了一个矩阵乘法的例子。在左边的这个矩阵的每一行,都代表了一种价目表;在右边的矩阵的每一列,都代表了一种做饭方式。那么所有可能的组合所最终产生的花费,则在结果矩阵中表示出来了。小明有一天成为了餐厅大厨,小红做掌柜兼管算账。我们假设物价不变。小红发现,如果今天买10斤肉花了A元,明天买20斤肉就得花2A元。如果买一斤肉要花C元,买1斤菜要花D元,那么买一斤肉和一斤菜就要花(C+D)元。每天小明汇报今日的材料消耗之后,小红便会将材料消耗转为需要花的钱数。如果材料消耗翻倍,花的钱数也翻倍。另外,如果去不同的菜市场,也会得到不同的花钱数量。小明每月送来一张长列表,里面是每日的材料消耗;而经过小红的处理,这张列表会转为每日,在不同的菜市场购买这些材料的花费。材料消耗翻倍,花费也翻倍。我们管这种从材料列表转为开销表的过程,就叫做一个线性映射。这也即是矩阵乘法的意义。最后补充一点。线性代数的引入方式因教材不同而不同。从代数学自身的体系来讲,可能从线性空间引入是相对完备的;但是从一般我们学习知识的理解顺序来讲,从线性方程组引入最为合适。因为只要还记得鸡兔同笼,就很容易理解线性方程组,从而推广到矩阵,然后是线性变换,线性空间。
以上出自: https://www.zhihu.com/question/21351965/answer/31050145

那么矩阵乘法的实现就是模拟手算的过程
 for( int  i = 0 ; i < n ; i++ )
    for( int j = 0 ; j < n ; j++ )
      for( int k = 0 ; k < n ; k++)
        c[i][j] += a[i][k] * b[k][j]; 


下面是介绍矩阵快速幂了:
只要知道快速幂的一般实现利用二进制思想将其离散化,那么矩阵快速幂无非就是将一般数字转化成矩阵,算法一致。

矩阵的快速幂问题,也能把它离散化?比如A^19  =>  (A^16)*(A^2)*(A^1),显然采取这样的方式计算时因子数将是log(n)级别的(原来的因子数是n),不仅这样,因子间也是存在某种联系的,比如A^4能通过(A^2)*(A^2)得到,A^8又能通过(A^4)*(A^4)得到,这点也充分利用了现有的结果作为有利条件。下面举个例子进行说明:

现在要求A^156,而156(10)=10011100(2) 

也就有A^156=>(A^4)*(A^8)*(A^16)*(A^128)  考虑到因子间的联系,我们从二进制10011100中的最右端开始计算到最左端。

#include <bits/stdc++.h>
#define MOD 1000000009
using namespace std;

struct Matrix
{
	int a[3][3];    //以3*3为例
}origin,res;           //res结果数组  origin随机生成的初始数组
void init()                  
{
     printf("Random Matrix:\n");
     for(int i=0;i<3;i++)           //生成随机数组
     {
             for(int j=0;j<3;j++)
             {
                     origin.a[i][j]=rand()%10;
                     printf("%8d",origin.a[i][j]);
             }
             printf("\n");
     }
     printf("\n");
     memset(res.a,0,sizeof(res.a));
     res.a[0][0]=res.a[1][1]=res.a[2][2]=1;     //将结果数组初始化为单元数组(和快速幂将保存结果的数初始化为1一样道理)
}
Matrix multiply( Matrix x , Matrix y ){   //矩阵乘法
	Matrix temp ;
	memset(temp.a,0,sizeof(temp.a));
	for( int i = 0 ; i < 3;  i++ ){
		for( int j = 0 ; j < 3 ; j++ ){
			for( int k = 0 ; k < 3 ; k++ ){
				temp.a[i][j] += x.a[i][k]*y.a[k][j];
				temp.a[i][j] %= MOD;
			}
		}
	}
	return temp;
}
void cal(int n){
	while(n){               //矩阵快速幂
		if( n & 1 ) res = multiply(res,origin);
		n>>=1;
		origin = multiply(origin,origin);
	}
	for( int i = 0 ; i < 3; i ++ ){          //打印结果
		for( int j = 0 ; j < 3 ; j++ ){
			printf("%8d",res.a[i][j]);
		}
		printf("\n");
	}
	cout<<endl;
}

int main(){
	int n;
	while(cin>>n){
		init();
		cal(n);
	}
	return 0;
}
输入n次方,计算结果如下:

例题:矩阵快速幂的应用:

基准时间限制: 1 秒 空间限制: 131072 KB 分值: 0 难度:基础题
收藏
关注
取消关注
斐波那契数列的定义如下:

F(0) = 0
F(1) = 1
F(n) = F(n - 1) + F(n - 2) (n >= 2)

(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, ...)
给出n,求F(n),由于结果很大,输出F(n) % 1000000009的结果即可。
Input
输入1个数n(1 <= n <= 10^18)。
Output
输出F(n) % 1000000009的结果。
Input示例
11
Output示例
89

AC代码:
#include <bits/stdc++.h>
#define LL long long
#define MOD 1000000009
using namespace std;

struct Matrix
{
	LL a[2][2];
}origin,res;
Matrix multiply( Matrix x , Matrix y ){
	Matrix temp ;
	memset(temp.a,0,sizeof(temp.a));
	for( int i = 0 ; i < 2;  i++ ){
		for( int j = 0 ; j < 2 ; j++ ){
			for( int k = 0 ; k < 2 ; k++ ){
				temp.a[i][j] += (x.a[i][k]%MOD*y.a[k][j]%MOD)%MOD;
				temp.a[i][j] %= MOD;
			}
		}
	}
	return temp;
}
void cal(LL n){
	origin.a[0][0] = origin.a[0][1] = 1;
    origin.a[1][0] = 1 ; origin.a[1][1] = 0;

    res.a[0][0] = res.a[1][1] = 1;
    res.a[0][1] = res.a[1][0] = 0;
	while(n){
		if( n & 1 ) res = multiply(res,origin);
		origin = multiply(origin,origin);
		n>>=1;
	}
	cout<<res.a[0][1]%MOD<<endl;
}
int main(){
	LL n;
	while(cin>>n){
		cal(n);
	}
	return 0;
}
代码很容易看懂,那么为什么能用矩阵乘法求斐波那契数列呢?
首先我们初始化这样一个矩阵:
11
10

然后res数组是这样的:

F[i-1]
F[i-2]

让这两个矩阵相乘   结果就相当于每次下面的替换上面的   

F[i-2] + F[i-1]  (F[i])
F[i-1]

自然就会得到斐波那契数列第N项f[n]。

并且有这样的一般式:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值