再谈矩阵快速幂
再谈矩阵快速幂
矩阵乘法
对于一个大小为
那么对于矩阵
形象化的,画个图。
上图中
代码实现:
Matrix operator * (const Matrix &a,const Matrix &b)
{
Matrix ans(0);
for(int i=1;i<=m;++i)
for(int j=1;j<=m;++j)
for(int k=1;k<=m;++k)
ans.v[i][j]=(ans.v[i][j]%Mod+a.v[i][k]%Mod*b.v[k][j]%Mod)%Mod;
return ans;
}
对于矩阵乘法满足结合律的证明
即对于任意三个矩阵
看看就行,知道满足结合律即可。自己证一遍没必要,看得头痛。
矩阵乘法的单位元
单位元的定义:对于一种运算
比如说加减法的单位元是
那么矩阵乘法的单位元呢?
可以自己验算一下,对于一个
即仅有对角线元素是
那么对于一个矩阵
快速幂
基础算法,可以用
那么对于矩阵也适用。合称即为矩阵快速幂。
搭配上矩阵乘法,就可以通过洛谷 P3390 【模板】矩阵快速幂。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const int N=105,Mod=1e9+7;
ljl k;int n;
struct M{
int v[N][N];
M(int x)
{
memset(v,0,sizeof(v));
for(int i=1;i<N;++i)
v[i][i]=x;
}
}a(0),r(1);
M operator * (const M &a,const M &b)
{
M r(0);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
for(int k=1;k<=n;++k)
r.v[i][j]=(r.v[i][j]+1ll*a.v[i][k]*b.v[k][j])%Mod;
return r;
}
M qpow(M a,ljl k)
{
M ans(1);
while(k)
{
if(k&1)ans=ans*a;
a=a*a;
k>>=1;
}
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>k;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
cin>>a.v[i][j];
r=qpow(a,k);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
cout<<r.v[i][j]<<' ';
cout<<'\n';
}
return 0;
}
矩阵快速幂的应用场景及一般使用步骤
在解决问题中,矩阵常用来存储状态和附带属性。
适用于一些满足以下条件的递推:
- 暴力
O(n) 递推时间不够。 - 可以滚动数组(如背包、Floyd)。
对于满足上述条件的场景,一般应用矩阵快速幂解决的步骤:
- 构造出基础矩阵,一般为
1\times n ,存储基本状态,设为a 。 - 构造转移矩阵
fac ,满足当a 存储上一个状态时,fac\times a 组成的矩阵存储当前状态。
构造好后,就可以联系上快速幂。
在一般情况下,状态矩阵大小为
假设我们要进行
比如说,从
一般来说,矩阵变化一次仅多算出一个元素。
形象的,是这样:
其中,答案为
那么
对于构造转移矩阵的一些技巧
此处状态矩阵大小为
上文提到,一般来说,矩阵变化一次仅多算出一个元素。
对于一次转移:
即对于每一个
那么
即转移可以表示为
那么我们就可以分析每一个
一些例题
例 1 :斐波那契数列
大家都知道,斐波那契数列是满足如下性质的一个数列:
求
那么对于基础的递推,时间
考虑矩阵快速幂。
设状态矩阵
那么我们每次转移就是把
那么我们一起构建下转移矩阵。设
先考虑转移后的
再看
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const ljl Mod=1e9+7;
ljl n;
struct M{
ljl v[5][5];
M(ljl x)
{
for(ljl i=1;i<=2;++i)
for(ljl j=1;j<=2;++j)
v[i][j]=(i==j?x:0);
}
}base(1),a(0);
M operator * (const M &a,const M &b)
{
M r(0);
for(ljl i=1;i<=2;++i)
for(ljl j=1;j<=2;++j)
for(ljl k=1;k<=2;++k)
r.v[i][j]=(r.v[i][j]+a.v[i][k]*b.v[k][j])%Mod;
return r;
}
M qpow(M a,ljl k)
{
M res(1);
while(k>0)
{
if(k&1)
res=res*a;
a=a*a;
k=k>>1;
}
return res;
}
int main(){
ios::sync_with_stdio(0);
cin>>n;
if(n<=2)
{
cout<<"1\n";return 0;
}
/*
0 1
1 1
*/
a.v[1][1]=0;a.v[1][2]=1;
a.v[2][1]=1;a.v[2][2]=1;
// for(int i=1;i<=2;++i)
// {
// for(int j=1;j<=2;++j)
// cout<<base.v[i][j]<<' ';
// cout<<'\n';
// }
M ans(0);ans=qpow(a,n);
cout<<ans.v[1][2]%Mod<<'\n';
return 0;
}
例 2 :走楼梯
有
其中
这题还能转化为维护一个序列,满足:
注意到
不难想到状态矩阵
接下来构造转移矩阵。
先看看目标:
注意到
特别的,
那么这题的步骤就是:
- 暴力搞出初始矩阵。
-
- 快速幂。
最后特判一下,如果
代码:
#include<bits/stdc++.h>
using namespace std;
using ljl = long long;
const ljl N=1e18+5;
const int M=105,Mod=1e9+7;
ljl n;int m;
struct Matrix{
ljl v[M][M];
Matrix(int x)
{
memset(v,0,sizeof(v));
for(int i=1;i<M;++i)
v[i][i]=x;
}
}base(1),ans(0);
Matrix operator * (const Matrix &a,const Matrix &b)
{
Matrix ans(0);
for(int i=1;i<=m;++i)
for(int j=1;j<=m;++j)
for(int k=1;k<=m;++k)
ans.v[i][j]=(ans.v[i][j]%Mod+a.v[i][k]%Mod*b.v[k][j]%Mod)%Mod;
return ans;
}
Matrix qpow(Matrix a,ljl p)
{
Matrix ans(1);
while(p)
{
if(p&1)ans=ans*a;
a=a*a;
p>>=1;
}
return ans;
}
int main(){
// ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;++i)
{
ans.v[1][i]=1;
for(int j=1;j<i;++j)
ans.v[1][i]=(ans.v[1][i]+ans.v[1][j])%Mod;
}
if(n<=m)
{
cout<<ans.v[1][n]<<'\n';
return 0;
}
// for(int i=1;i<=m;++i)
// cout<<ans.v[1][i]<<' ';
// cout<<'\n';
Matrix fac(0);
for(int i=1,cnt=2;i<=m;++i)//lie
{
if(i!=m)
{
fac.v[cnt][i]=1;
++cnt;
}
else
{
for(int j=1;j<=m;++j)
fac.v[j][i]=1;
}
}
// for(int i=1;i<=m;++i)
// {
// for(int j=1;j<=m;++j)
// cout<<fac.v[i][j]<<' ';
// cout<<'\n';
// }
Matrix tmp=qpow(fac,n-m);
// cout<<"111\n";
ans=ans*tmp;
cout<<ans.v[1][m]%Mod<<'\n';
return 0;
}