---
# System prepended metadata

title: 矩陣快速冪

---

# 矩陣快速冪

用來求矩陣的n次方，方法與[快速冪](/OBXWeBUOTH-Eo5FIcMIHDA)相似，所以我們先寫個快速冪

求A^b^次方:
```cpp=
int powf(int a,int b){
    int ans=1;
    while(b){
        if(b & 1) ans*=a;
        a*=a;
        b>>=1;
    }
    return ans;
}
```

## 矩陣快速冪
矩陣快速冪只是把整數換成矩陣而已，其概念是一樣的，差別在於要寫一個矩陣相乘的函式。

求(矩陣a)^b^:


>先副上Code後面再來解釋
```cpp=
#define matrix vector<vector<int>> 
#define N 矩陣大小(N*N)

matrix matrix_multiply(matrix &a,matrix &b){  //矩陣相乘函式
    matrix ans(N,vector<int>(N));
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            for(int k=0;k<N;k++){
                ans[i][j]+=a[i][k]*b[k][j];
            }
        }
    }
    return ans;
}

matrix matrix_powf(matrix a,int b){
    matrix ans(N,vector<int>(N));
    for(int i=0;i<N;i++){
        ans[i][i]=1;
    }
    
    while(b){
        if(b & 1) ans=matrix_multiply(ans,a);
        a=matrix_multiply(a,a);
        b>>=1;
    }
    return ans;
}
```

在matrix_powf()中，有3點被更改，分別是
1. 初始化ans

在快速冪中，ans初始化為1，因為要把b在二進位表示法中最後一位為1時，把a乘到答案裡，但在矩陣快速冪裡，初始化的ans就要換乘矩陣裡的1，也就是[[1,0];[0,1]] (以下都稱作A)，驗算一下就可得知

```cpp=
1  2   1  0    1*1+2*0  1*0+2*1   1  2
     x       =                  =
3  4   0  1    3*1+4*0  3*0+4*1   3  4
```

所以任何2*2的矩陣乘上A都會是自己，藉由觀察A的1是由左上往右下斜的，所以在初始化可先把A設為0，再用for跑一遍把斜線上給的點都設成1。

```cpp=
matrix ans(N,vector<int>(N,0));
for(int i=0;i<N;i++){
    ans[i][i]=1;
}
```

2. 把答案乘到ans裡

在原本快速冪的if(b & 1) ans*=a中，要把a乘到答案裡很簡單，但在矩陣中，要寫一個函式把矩陣相乘的結果傳回來，再把它賦予給原本的矩陣。

```cpp=
if(b & 1) ans=matrix_multiply(ans,a);
```

3. 矩陣平方

同上的概念,也是把矩陣丟到matrix_multiply，再把回傳的結果賦予給b。
```cpp=
b=matrix_multiply(b,b);
```

## 費式數列
$f(0)=0,f(1)=1,f(2)=1,f(3)=2,f(4)=3,f(5)=5$

如果要求費式數列有很多方法：

>top down
```cpp=
int f(int n){
    if(n==0 || n==1) return 1;
    return f(n-1)+f(n-2);
}
```

>botton up
```cpp=
int f(int n){
    vector<int> fi(n+1);
    fi[0]=1;
    fi[1]=1;
    for(int i=2;i<=n;i++){
        fi[i]=fi[i-1]+fi[i-2];
    }
    return fi[n]
}

```

但如果要求費式數列第1e9個mod m呢? 如果用上述兩種方法時間複雜度肯定會爆掉，這時候矩陣快速冪就派上用場了。

我們可以先把f1和f2寫成矩陣的形式(以下稱為初始矩陣)
```cpp=
[f(1) f(2)]
```
這時當我們乘上矩陣[[0,1];[1,1]] (以下都稱為A)的話
```cpp=
              0  1   
[f(1) f(2)] x      = [f(1)*0+f(2)*1 f(1)*1+f(2)*1] = [f(2) f(3)]
              1  1
```
如果再乘以A
```cpp=
              0  1   
[f(2) f(3)] x      = [f(2)*0+f(3)*1 f(2)*1+f(3)*1] = [f(3) f(4)]
              1  1
```
如果要得到[f(3) f(4)]就得把[f(1) f[2]]乘上A的2次方
```cpp=
              0  1   0  1  
[f(1) f(2)] x      x       = [f(3) f(4)]
              1  1   1  1
```
每多乘A一次，數列都會往後移一格
<b>結論 : 如果想得到[f(n) f(n+1)]就必須把[f(1) f(2)]乘上A^(n-1)^</b>

最後就回到了上面的矩陣快速冪，但為了方便撰寫，初始矩陣可以從費式數列第0項開始，A就可以很直觀的乘上n次而非n-1，這樣最後初始矩陣乘上A的時候，A[1][0]就是答案了。
```cpp=
//R=A的n次方
                              R[0][0] R[0][1]
[f(n) f(n+1)] = [f(0) f(1)] x                  
                              R[1][0] R[1][1]
                              
= [f(0)*R[0][0]+f(1)*R[1][0] f(0)*R[0][1]+f(1)*R[1][1]]

= [0*R[0][0]+1*R[1][0] 0*R[0][1]+1*R[1][1]]

= [R[1][0] R[1][1]]
```

#### 完整Code

```cpp=
#include <bits/stdc++.h>
#define matrix vector<vector<int>> 
using namespace std;

matrix matrix_multiply(matrix &a,matrix &b){  
    matrix ans(2,vector<int>(2));
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                ans[i][j]+=a[i][k]*b[k][j];
            }
        }
    }
    return ans;
}

matrix matrix_powf(matrix a,int b){
    matrix ans(2,vector<int>(2));
    for(int i=0;i<2;i++){
        ans[i][i]=1;
    }
    while(b){
        if(b & 1) ans=matrix_multiply(ans,a);
        a=matrix_multiply(a,a);
        b>>=1;
    }
    return ans;
}

int f(int n){
    //建立A模板
    matrix A(2,vector<int>(2,1));
    A[0][0]=0;
    
    matrix result=matrix_powf(A,n);
    return result[1][0];
}
int main(){
    int n;
    while(cin >> n){
        cout << f(n) << '\n';
    }
    return 0;
}
```
#### 題目:<a href="https://zerojudge.tw/ShowProblem?problemid=b525">Zerojudge : b525 先別管這個了，你聽過turtlebee嗎？</a>

#### 費氏數列大數實作
當第n項沒有到很大時，矩陣快速冪反而比較慢

:::spoiler Code
```cpp=
#include <bits/stdc++.h>
#define matrix vector<vector<string>>
using namespace std;

string add(string a,string b){
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());
    
    string ans="";
    int rest=0;
    for(int i=0;i<min(a.size(),b.size());i++){
        int cnt=a[i]-'0'+b[i]-'0'+rest;
        rest=0;
        if(cnt>9){
            rest=cnt/10;
            cnt=cnt%10;
        }
        ans+=(cnt+'0');
    }
    for(int i=min(a.size(),b.size());i<max(a.size(),b.size());i++){
        int cnt=rest;
        if(a.size() > b.size()) cnt+=a[i]-'0';
        else cnt+=b[i]-'0'; 
        rest=0;
        if(cnt>9){
            rest=cnt/10;
            cnt=cnt%10;
        }
        ans+=(cnt+'0');
    }
    if(rest!=0) ans+=(rest+'0');
    reverse(ans.begin(),ans.end());
    return ans;
}

string mul(string a,string b){
    reverse(a.begin(),a.end());
    reverse(b.begin(),b.end());

    string ans;
    vector<int> ad(a.size()+b.size()+1,0);
    
    for(int i=0;i<a.size();i++){
        for(int j=0;j<b.size();j++){
            int cnt=(a[i]-'0')*(b[j]-'0');
            ad[i+j]+=cnt;
        }
    }

    for(int i=0,rest=0;i<ad.size();i++){
        int cnt=ad[i]+rest;
        rest=0;
        if(cnt>9){
            rest=cnt/10;
        }
        ans+=(cnt%10)+'0';
    }
    int i=ans.size()-1;
    for(;i>=0;i--){
        if(ans[i]!='0') break;
    }
    ans=ans.substr(0,i+1);

    reverse(ans.begin(),ans.end());
    return ans;
}

matrix maxtrix_mul(matrix& a,matrix& b){
    matrix ans(2,vector<string>(2,"0"));
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                ans[i][j]=add(ans[i][j],mul(a[i][k],b[k][j]));
            }
        }
    }
    return ans;
}


matrix matrix_pow(matrix& A,int n){
    matrix ans(2,vector<string>(2,"0"));
    ans[0][0]="1";ans[1][1]="1";
    while(n){
        if(n & 1){
            ans=maxtrix_mul(ans,A);
        }
        n >>= 1;
        A=maxtrix_mul(A,A);
    }
    return ans;
}

string fi(int n){
    if(n==0) return "0";
    
    matrix A(2,vector<string>(2,"1"));
    A[0][0]="0";
    matrix result=matrix_pow(A,n);
    return result[1][0];
}

int main(){
    int n;
    while(cin >> n){
        cout << "The Fibonacci number for " << n <<  " is " << fi(n) << '\n';
    }    
    return 0;
}
```
:::

#### 費氏數列小知識
1. 當題目要取F(i) mod 1e9+9時是有周期的，週期為333333336，f(333333336)=0
2. 費氏數列有公式解，如下
![](https://i.imgur.com/voshwx8.png)

## 進階運用
題目:<a href="https://zerojudge.tw/ShowProblem?problemid=e811">e811: 3. 密碼產生器 (Password)</a>

三階矩陣快速冪應用
根據公式:A(N)＝P×A(N-1)＋Q×A(N-2)＋R
```
  A2 0 0       P Q R       A1 0 0
[ A1 0 0 ] = [ 1 0 0 ] * [ A0 0 0 ]
  1  0 0       0 0 1       1  0 0
  
                      ^(n-1)
  An   0 0       P Q R       A1 0 0
[ An-1 0 0 ] = [ 1 0 0 ] * [ A0 0 0 ]
  1    0 0       0 0 1       1  0 0
  
```
Code:
```cpp=
#include <bits/stdc++.h>
#define int long long
#define matrix vector<vector<int>>
using namespace std;

matrix mul(matrix &a,matrix &b){
    matrix ans(3,vector<int>(3));
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            for(int k=0;k<3;k++){
                ans[i][j]=(ans[i][j]+a[i][k]*b[k][j])%(long long)1e9;
            }
        }
    }
    return ans;
}

signed main(){
    int P,Q,R,A0,A1,N;
    cin >> P >> Q >> R >> A0 >> A1 >> N;
    matrix ans(3,vector<int>(3));
    ans[0][0]=A1,ans[1][0]=A0,ans[2][0]=1;    
    
    matrix p(3,vector<int>(3));
    p[0][0]=P,p[0][1]=Q,p[0][2]=R,p[1][0]=1,p[2][2]=1;

    N--;
    while(N){
        if(N & 1){
            ans=mul(p,ans);
        }
        p=mul(p,p);
        N >>= 1;
    }
    cout << setw(4) << setfill('0') << ans[0][0] << '\n';
    return 0;
}
```