矩阵乘法
算法学习
模板学习
前言: 矩阵快速幂可以优化 d p dp dp ,加快递推式。
矩阵乘法: C i j = ∑ k = 1 n A i k × B k j C_{ij}=\sum_{k=1}^n A_{ik}\times B_{kj} Cij=∑k=1nAik×Bkj
模板: P 3390 P3390 P3390
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define X 2
const int P=1e9+7;
struct Mat {
vector<vector<LL> >a;
Mat(){
a.resize(X,vector<LL>(X,0));
}
Mat(vector<vector<LL> >b){
a=b;
}
Mat one(){
Mat a;
for(int i=0;i<X;i++)a.a[i][i]=1;
return a;
}
Mat operator *(const Mat &obj){
Mat ret;
for(int i=0; i<X; i++) {
for(int j=0; j<X; j++) {
for(int k=0; k<X; k++) {
ret.a[i][j]=(ret.a[i][j]+a[i][k]*obj.a[k][j])%P;
}
}
}
return ret;
}
Mat ksm(Mat a,LL b){
Mat ret=one();
while(b){
if(b&1)ret=ret*a;
a=a*a;
b>>=1;
}
return ret;
}
};
优化思路
A t = A t − 1 × C = > A t = A 1 × C t − 1 A_t=A_{t-1}\times C=>A_t=A_1\times C^{t-1} At=At−1×C=>At=A1×Ct−1
由于符合分配律,因此可以通过计算 C t − 1 C^{t-1} Ct−1 来加速计算 A t A_t At 。
时间复杂度: O ( t n 3 ) O(tn^3) O(tn3)
博客
十个利用矩阵乘法解决的经典题目 | Matrix67: The Aha Moments
练习1:加速dp递推式
例题1:P1962
题目描述: f 1 = f 2 = 1 , f i = f i − 1 + f i − 2 f_1=f_2=1,f_i=f_{i-1}+f_{i-2} f1=f2=1,fi=fi−1+fi−2 。求 f n ( m o d m o d ) f_n\pmod {mod} fn(modmod) 。 n < 2 63 n<2^{63} n<263 。
问题分析:
- ( f i , f i + 1 ) × [ 0 1 1 1 ] = ( f i + 1 , f i + 2 ) (f_i,f_{i+1})\times \left[\begin{matrix}0&1\\1&1\end{matrix}\right]=(f_{i+1},f_{i+2}) (fi,fi+1)×[0111]=(fi+1,fi+2)
- 即可得到: ( f n , f n + 1 ) = ( f 1 , f 2 ) × [ 0 1 1 1 ] n − 1 (f_n,f_{n+1})=(f_1,f_2)\times \left[\begin{matrix}0&1\\1&1\end{matrix}\right]^{n-1} (fn,fn+1)=(f1,f2)×[0111]n−1
void solve(){
LL n;
cin>>n;
Mat a({{0,1},{1,1}});
a=a.ksm(a,n-1);
cout<<(a.a[0][0]+a.a[1][0])%P<<'\n';
}
例题2:P2044
题目描述: 给定 f 0 f_0 f0 ,已知 f i = ( t × f n − 1 + p ) ( m o d P 1 ) f_i=(t\times f_{n-1}+p)\pmod {P1} fi=(t×fn−1+p)(modP1) 。求 f ( n ) ( m o d P 2 ) f(n)\pmod {P2} f(n)(modP2) 。 n , P 1 ∈ [ 1 , 1 e 18 ] , t , p , f 0 ∈ [ 0 , 1 e 18 ] , P 2 ∈ [ 1 , 1 e 8 ] n,P1\in[1,1e18],t,p,f_0\in[0,1e18],P2\in[1,1e8] n,P1∈[1,1e18],t,p,f0∈[0,1e18],P2∈[1,1e8] 。
问题分析:
- ( f i , f i + 1 ) × [ t 0 1 1 ] = ( f i + 1 , f i + 2 ) (f_i,f_{i+1})\times \left[\begin{matrix}t&0\\1&1\end{matrix}\right]=(f_{i+1},f_{i+2}) (fi,fi+1)×[t101]=(fi+1,fi+2)
例题3:P1939
题目描述: f 1 = f 2 = f 3 = 1 , f i = f i − 1 + f i − 3 f_1=f_2=f_3=1,f_i=f_{i-1}+f_{i-3} f1=f2=f3=1,fi=fi−1+fi−3 。求 f ( n ) ( m o d 1 e 9 + 7 ) f(n)\pmod{1e9+7} f(n)(mod1e9+7) 。 T < = 100 , 1 < = n < = 1 e 9 T<=100,1<=n<=1e9 T<=100,1<=n<=1e9 。
问题分析:
- ( f i , f i + 1 , f i + 2 ) × [ 0 0 1 1 0 0 0 1 1 ] = ( f i + 1 , f i + 2 , f i + 3 ) (f_i,f_{i+1},f_{i+2})\times \left[\begin{matrix}0&0&1\\1&0&0\\0&1&1\end{matrix}\right]=(f_{i+1},f_{i+2},f_{i+3}) (fi,fi+1,fi+2)× 010001101 =(fi+1,fi+2,fi+3)
void solve(){
LL n;
cin>>n;
Mat a({{0,0,1},{1,0,0},{0,1,1}});
a=a.ksm(a,n-1);
cout<<(a.a[0][0]+a.a[1][0]+a.a[2][0])%P<<'\n';
}
图上加速
例题1:
题目描述: 给定 n n n 个点 m m m 条边的有向图。求 t t t 秒后, 1 1 1 到 n n n 的不同路径数有多少种。
问题分析:
- 设 A A A 为邻接矩阵,其中 A i j = 1 A_{ij}=1 Aij=1 表示存在 i − > j i->j i−>j 的边。
- 设 C i j = ∑ k = 1 n A i k × A k j C_{ij}=\sum_{k=1}^nA_{ik}\times A_{kj} Cij=∑k=1nAik×Akj ,则 C i j C_{ij} Cij 表示 i i i 到 j j j 经过两条边的路径数量。
- 则 A t [ 1 ] [ n ] A^t[1][n] At[1][n] 就是答案。
例题2:P5789
题目描述: 给定 n n n 个点 m m m 条边的无向图。初始在点 1 1 1 ,没秒钟可能会发生:爆炸,原地不动,从某条边走向下一个点。求 t t t 秒后。行为方案数有多少种。答案对 2017 2017 2017 取模。
问题分析:
- 每个点连个自环,来维护:原地不同
- 每个点向 0 0 0 连边并让 0 0 0 连一个自环,来维护:爆炸。
- 然后让套路做即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int P=2017;
int n,m;
struct Mat {
vector<vector<LL> >a;
Mat(){
a.resize(n+1,vector<LL>(n+1,0));
}
Mat(vector<vector<LL> >b){
a=b;
}
Mat one(){
Mat a;
for(int i=0;i<=n;i++)a.a[i][i]=1;
return a;
}
Mat operator *(const Mat &obj){
Mat ret;
for(int i=0; i<=n; i++) {
for(int j=0; j<=n; j++) {
for(int k=0; k<=n; k++) {
ret.a[i][j]=(ret.a[i][j]+a[i][k]*obj.a[k][j])%P;
}
}
}
return ret;
}
Mat ksm(Mat a,LL b){
Mat ret=one();
while(b){
if(b&1)ret=ret*a;
a=a*a;
b>>=1;
}
return ret;
}
};
void solve(){
cin>>n>>m;
vector<vector<LL> >a(n+1,vector<LL>(n+1));
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
a[u][v]=1;
a[v][u]=1;
}
for(int i=0;i<=n;i++)a[i][i]=1;
for(int i=1;i<=n;i++)a[i][0]=1;
Mat x(a);
LL t;
cin>>t;
x=x.ksm(x,t);
LL ans=0;
for(int i=0;i<=n;i++)ans=(ans+x.a[1][i])%P;
cout<<ans;
}