矩阵快速幂算法
矩阵快速幂的本质就是 矩阵+快速幂,思路没什么变化。
快速幂的思路见之前的 快速幂介绍,这里就不多说了。
至于矩阵的话,知道矩阵是啥,怎么算乘法就可以了。
相关矩阵基础
注:已经了解矩阵的可以跳过本部分内容。
简单来说
n
∗
m
n*m
n∗m 的矩阵就是个
n
n
n 行
m
m
m 列 的大方块。
矩阵要进行乘法运算必须保证第一个矩阵的列数=第二个矩阵的行数。
也就是说
n
1
∗
m
1
n_1 * m_1
n1∗m1 的矩阵 a 与
n
2
∗
m
2
n_2 * m_2
n2∗m2 的矩阵 b 相乘,需要保证
m
1
=
n
2
m_1=n_2
m1=n2 最终得到的结果为
n
1
∗
m
2
n_1*m_2
n1∗m2 的矩阵 c。
对于得到的矩阵 c ,它每个位置的结果为第一个矩阵的第
i
i
i 行 与第二个矩阵的第
j
j
j 列相乘求和得到。
即结果矩阵中
i
i
i 行
j
j
j 列的元素
c
[
i
]
[
j
]
=
∑
k
=
1
m
1
a
[
i
]
[
k
]
∗
b
[
k
]
[
j
]
c[i][j]=\sum_{k=1}^{m_1}a[i][k]*b[k][j]
c[i][j]=∑k=1m1a[i][k]∗b[k][j]
具体例子:
[ 1 2 ] ∗ [ 1 2 3 4 ] = [ 1 ∗ 1 + 2 ∗ 3 1 ∗ 2 + 2 ∗ 4 ] = [ 7 6 ] \begin{bmatrix} 1 & 2 \end{bmatrix}* \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}= \begin{bmatrix} 1*1+2*3 & 1*2+2*4 \end{bmatrix}= \begin{bmatrix} 7 & 6 \end{bmatrix} [12]∗[1324]=[1∗1+2∗31∗2+2∗4]=[76]
矩阵快速幂
只需要在原来快速幂的基础上,把数字换成矩阵即可。
以下为原版整数的快速幂:
#include <bits/stdc++.h>
using namespace std;
int main()
{
long long a,b,c,ans;
while(cin>>a>>b>>c)
{
ans=1;
while(b>0)
{
if(b&1)ans=ans*a%c;
a=a*a%c;
b>>=1;
}
cout<<ans<<endl;
}
return 0;
}
代码以洛谷 P3390 模板题为例,关键部分在 qpow 函数中。
为保证原有结构尽可能不被修改,矩阵快速幂代码矩阵乘法部分放在重载运算符中。
Matrix::unit 为单位矩阵,这里在读入数据时顺便初始化了,也可以在 qpow 中进行初始化。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,mod=1e9+7;
class Matrix
{
public:
static Matrix unit;
ll data[100][100];
int n,m;
Matrix operator *(Matrix &mat)
{
Matrix tmp=Matrix::unit;
tmp.n=this->n;
tmp.m=mat.m;
for(int i=0;i<this->n;i++)
{
for(int j=0;j<mat.m;j++)
{
tmp.data[i][j] = 0;
for(int k=0;k<this->m;k++)
{
tmp.data[i][j] += this->data[i][k]*mat.data[k][j];
tmp.data[i][j] %= mod;
}
}
}
return tmp;
}
void print()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(j!=0)cout<<" ";
cout<<this->data[i][j];
}
cout<<"\n";
}
}
};
Matrix Matrix::unit;
Matrix mat;
Matrix qpow(Matrix m1,ll b,ll mod)
{
Matrix ans=Matrix::unit;
while(b>0)
{
if(b&1)ans=ans*m1;
m1=m1*m1;
b>>=1;
}
return ans;
}
int main()
{
cin>>n>>k;
mat.n=mat.m=n;
Matrix::unit.n=Matrix::unit.m=n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>mat.data[i][j];
}
Matrix::unit.data[i][i]=1;
}
mat=qpow(mat,k,mod);
mat.print();
return 0;
}
补充说明:
如果题目涉及多次取模,建议将矩阵乘法写成 Matrix 的成员函数或者单独一个乘法函数。
应用:快速计算斐波那契数列
斐波那契数列定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
如果需要求的 n 值很大,可能会在递推时超时 ,此时可以利用矩阵快速幂求解。
首先将递推式构造成矩阵形式:
[
F
(
n
)
F
(
n
−
1
)
]
=
[
1
1
1
0
]
∗
[
F
(
n
−
1
)
F
(
n
−
2
)
]
\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}
[F(n)F(n−1)]=[1110]∗[F(n−1)F(n−2)]
这样计算数列的第 n 项时可以利用中间矩阵的 n-1 次幂乘上数列的前两项:
[
F
(
n
)
F
(
n
−
1
)
]
=
[
1
1
1
0
]
n
−
1
∗
[
F
(
1
)
F
(
0
)
]
\begin{bmatrix} F(n) \\ F(n-1) \end{bmatrix}= \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^{n-1}* \begin{bmatrix} F(1) \\ F(0) \end{bmatrix}
[F(n)F(n−1)]=[1110]n−1∗[F(1)F(0)]
因为 F(0)=0 F(1)=1,所以只需要快速计算中间的矩阵,矩阵的第一行第一列位置就是要计算的 F(n)。
由于快速幂文章中介绍的模运算性质,如果题目需要计算时需要对斐波那契数列取模,那么可以直接在矩阵运算过程中进行取模。
题目可以参考 NEFU OJ 2348 小蓝与斐波拉契数列