矩阵加速


矩阵加速

【模板】矩阵加速(数列)

作用

有这样一种问题,要你求斐波那契数列的第$n$项,但是这个$n$可以大到$int64$的大小($2^{61}-1$),还会有若干次询问,这么多次询问还要你1s内出所有答案,传统的for循环肯定GG,于是这时候就需要矩阵加速啦(利用矩阵运算+矩阵快速幂来加速递推求解过程)

  • 那么这里就需要特别注意一点,那就是矩阵的左乘和右乘,这一点在快速幂函数中必须必须要注意,如果写反了就错了(按照个人习惯来,我比较习惯构造一个矩阵,然后让初始向量左乘它)

原理

对于模板题,我们可以很容易得到这样一个矩阵,它满足

于是,我们观察左侧向量的第一个数,初始为$a_1$,左乘一次矩阵得到$a_2$,所以左乘$n-1$次矩阵可以得到$a_n$,应用矩阵快速幂,打出如下代码

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
struct M{
ll mat[15][15];
}a;
M operator*(M x1,M x2){
M res;
memset(res.mat,0,sizeof(res));
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
for(int k=1;k<=3;k++){
res.mat[i][j]+=(x1.mat[i][k]%mod*x2.mat[k][j]%mod)%mod;
res.mat[i][j]%=mod;
}
}
}
return res;
}

inline M qpow(M a,ll y,M ans){
M base=a;
while(y>0){
if(y&1) ans=ans*base;
y>>=1;
base=base*base;
}
return ans;
}

int main(){
int t;
cin>>t;
while(t--){
ll nn;
cin>>nn;
memset(a.mat,0,sizeof(a));
M b;
memset(b.mat,0,sizeof(b));
b.mat[1][1]=b.mat[1][2]=b.mat[1][3]=1;
a.mat[1][1]=a.mat[1][2]=a.mat[2][2]=a.mat[2][3]=a.mat[3][1]=0;
a.mat[1][3]=a.mat[2][1]=a.mat[3][2]=a.mat[3][3]=1;
M ans=qpow(a,nn-1,b);
cout<<ans.mat[1][1]<<endl;
}
return 0;
}

习题

洛谷 P1962 斐波那契数列

观察得矩阵

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
struct M{
ll mat[15][15];
}a;
M operator*(M x1,M x2){
M res;
memset(res.mat,0,sizeof(res));
for(int i=1;i<=2;i++){
for(int j=1;j<=2;j++){
for(int k=1;k<=2;k++){
res.mat[i][j]+=(x1.mat[i][k]%mod*x2.mat[k][j]%mod)%mod;
res.mat[i][j]%=mod;
}
}
}
return res;
}

inline M qpow(M a,ll y,M ans){
M base=a;
while(y>0){
if(y&1) ans=ans*base;
y>>=1;
base=base*base;
}
return ans;
}

int main(){
ll nn;
cin>>nn;
memset(a.mat,0,sizeof(a));
M b;
memset(b.mat,0,sizeof(b));
b.mat[1][1]=b.mat[1][2]=1;
a.mat[1][1]=0;
a.mat[1][2]=a.mat[2][1]=a.mat[2][2]=1;
M ans=qpow(a,nn-1,b);
cout<<ans.mat[1][1]<<endl;
return 0;
}

洛谷 P1349 广义斐波那契数列

就是把系数改成了$p$和$q$而已,太简单了,矩阵转移如下

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll p,q,a1,a2,n,mod;
struct M{
ll mat[15][15];
}a;
M operator*(M x1,M x2){
M res;
memset(res.mat,0,sizeof(res));
for(int i=1;i<=2;i++){
for(int j=1;j<=2;j++){
for(int k=1;k<=2;k++){
res.mat[i][j]+=(x1.mat[i][k]%mod*x2.mat[k][j]%mod)%mod;
res.mat[i][j]%=mod;
}
}
}
return res;
}

inline M qpow(M a,ll y,M ans){
M base=a;
while(y>0){
if(y&1) ans=ans*base;
y>>=1;
base=base*base;
}
return ans;
}

int main(){
cin>>p>>q>>a1>>a2>>n>>mod;
memset(a.mat,0,sizeof(a));
M b;
memset(b.mat,0,sizeof(b));
b.mat[1][1]=a1;
b.mat[1][2]=a2;
a.mat[1][1]=0;
a.mat[1][2]=q;
a.mat[2][1]=1;
a.mat[2][2]=p;
M ans=qpow(a,n-1,b);
cout<<ans.mat[1][1]<<endl;
return 0;
}