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

来源:互联网 发布:sublime windows 破解 编辑:程序博客网 时间:2024/06/18 04:24

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

小明今天要做饭,消耗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 1000000009using 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次方,计算结果如下:

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

1242斐波那契数列的第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 1000000009using 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;}
代码很容易看懂,那么为什么能用矩阵乘法求斐波那契数列呢?
首先我们初始化这样一个矩阵:
1110

然后res数组是这样的:

F[i-1]

F[i-2]

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

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

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

并且有这样的一般式:


阅读全文
1 0
原创粉丝点击