矩阵快速幂

矩阵快速幂(入门)

定义及模板

对于方阵存在求幂运算的概念。对于单个数值,我们通过将指数进行二进制拆分的思想将求幂运算的复杂度降至log(n),这种思想同样可以应用到方阵的求幂运算中。事实上,方阵的快速幂与单个数值的快速幂的写法完全一致,只需要对于乘法、取模进行运算符重载即可。

下面给出矩阵快速幂的模板

#include <cstdio>
#include <cstdlib>

using namespace std;

#define N 5+1 
// 最大矩阵规模,根据题目修改

typedef long long ll;
const ll mod = 1e9+7; // 取模

struct Matrix{
    int x;
    int y;
    ll a[N][N];
    Matrix(){};
    Matrix(int m,int n){
        x = m;
        y = n;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                a[i][j] = 0;
            }
        }
    }

    ll* operator[](int x){
        return a[x];
    }

    void ones(){
        for(int i = 1; i <= x; i++){
            a[i][i] = 1;
        }
    }
    Matrix operator *(Matrix b){
        Matrix ans(x,b.y);
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= b.y; j++){
                for(int k = 1; k <= y; k++){
                    ans[i][j] += a[i][k] * b[k][j];
                }
                ans[i][j] %= mod;
            }
        }
        return ans;
    }

    Matrix operator %(ll md){
        Matrix ans = *this;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                ans[i][j] %= md;
            }
        }
        return ans;
    }

    Matrix operator ^(ll p){
        Matrix ans(x,x);
        Matrix base = *this;
        ans.ones();
        while(p){
            if(p&1){
                ans = ans * base;
                ans = ans % mod;
            }
            base = base * base;
            base = base % mod;
            p >>= 1;
        }
        return ans;
    }
};


int main(){

    return 0;
}

应用:矩阵加速

矩阵加速用于线性方程的加速递推

level 1:简单线性递推

  • e.g1 : 求Fibonacci(n)%mod,mod=1e9+7

考虑到斐波那契数列的递推公式

f(n) = f(n-1) + f(n-2);

我们可以将数列的第n项、第n-1项、第n-2项组合成矩阵形式

// 数列第n、n-1、n-2的组合
   [f(n)   f(n-1)   f(n-2)]
// 数列第n+1、n、n-1的组合
   [f(n+1) f(n)     f(n-1)]

这两个矩阵满足关系式子

[left[ egin{matrix} a_{n}&a_{n-1}&a_{n-2} end{matrix} ight] * left[ egin{matrix} 1 & 1 & 0\ 1 & 0 & 1\ 0 & 0 & 0 end{matrix} ight] = left[ egin{matrix} a_{n+1}&a_{n}&a_{n-1} end{matrix} ight] ]

进而得出

[left[ egin{matrix} 2 & 1 & 1 end{matrix} ight] * left[ egin{matrix} 1 & 1 & 0\ 1 & 0 & 1\ 0 & 0 & 0 end{matrix} ight]^{n-2} = left[ egin{matrix} a_{n}&a_{n-1}&a_{n-2} end{matrix} ight] ]

因此,要求fibonacci(n),通过矩阵加速可以将O(n)降至O(log(n))


本题关系式为

[left[ egin{matrix} 2&1&1&1 end{matrix} ight] * left[ egin{matrix} 1&0&0&0\ 0&1&1&0\ 1&0&0&1\ 0&1&0&0 end{matrix} ight]^{n-4} = left[ egin{matrix} a_{n}&a_{n-1}&a_{n-2}&a_{n-3} end{matrix} ight] ]

#include <cstdio>
#include <cstdlib>

using namespace std;

#define N 5+1 
// 最大矩阵规模,根据题目修改

typedef long long ll;
const ll mod = 1e9+7;

struct Matrix{
    int x;
    int y;
    ll a[N][N];
    Matrix(){};
    Matrix(int m,int n){
        x = m;
        y = n;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                a[i][j] = 0;
            }
        }
    }

    ll* operator[](int x){
        return a[x];
    }

    void ones(){
        for(int i = 1; i <= x; i++){
            a[i][i] = 1;
        }
    }
    Matrix operator *(Matrix b){
        Matrix ans(x,b.y);
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= b.y; j++){
                for(int k = 1; k <= y; k++){
                    ans[i][j] += a[i][k] * b[k][j];
                }
                ans[i][j] %= mod;
            }
        }
        return ans;
    }

    Matrix operator %(ll md){
        Matrix ans = *this;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                ans[i][j] %= md;
            }
        }
        return ans;
    }

    Matrix operator ^(ll p){
        Matrix ans(x,x);
        Matrix base = *this;
        ans.ones();
        while(p){
            if(p&1){
                ans = ans * base;
                ans = ans % mod;
            }
            base = base * base;
            base = base % mod;
            p >>= 1;
        }
        return ans;
    }
};


int main(){
    int t;
    scanf("%d",&t);

    Matrix k(4,4);
    k[1][1] = 1;
    k[2][2] = 1;
    k[2][3] = 1;
    k[3][1] = 1;
    k[3][4] = 1;
    k[4][2] = 1;

    Matrix s(1,4);
    s[1][1] = 2;
    s[1][2] = 1;
    s[1][3] = 1;
    s[1][4] = 1;
    while(t--){
        ll n;
        scanf("%lld",&n);
        if(n <= 3){
            printf("1
");
        }else{
            Matrix ans = s * (k^(n-4));
            printf("%lld
",ans[1][1]);
        }
    }

    return 0;
}

level 2: 带有求和项的简单递推

由于矩阵加速题难点在于关系矩阵的寻找,而不在于代码实现,为了节省篇幅,之后的题解不再出现具体代码实现。

e.g1:HDU-3306 Another kind of Fibonacci

本题的关系矩阵略难推导,应该注意到

[egin{align} & S_{n+1} = S_{n} + A_{n+1}^2\ & A_{n+2}^2 = (xA_{n+1} + yA_{n})^2 = x^2A_{n+1}^2 + y^2A_n^2 + 2xyA_{n+1}A_n \ & A_{n+2}A_{n+1} = (xA_{n+1}+yA_{n})A_{n+1} = xA_{n+1}^2 + yA_{n+1}A_n end{align} ]

因此可以得到

[left[ egin{matrix} S_n & A_{n+1}^2 & A_n^2 & A_{n+1}A_n end{matrix} ight] * left[ egin{matrix} 1 & 0 & 0 & 0\ 1 & x^2 & 1 & x\ 0 & y^2 & 0 & 0\ 0 & 2xy & 0 & y end{matrix} ight] = left[ egin{matrix} S_{n+1}^2 & A_{n+2}^2 & A_{n+1}^2 & A_{n+2}A_{n+1} end{matrix} ight] ]

本题小结

  1. 对于求和项有 (S_{n+1}-S_n = ...)
  2. 如果递推公式中出现了别的项,无法通过已有项的线性组合得出(如(A_{n+1}A_n)项无法通过(A_{n+1})(A_n)的线性组合得出),那么考虑更改递推公式或在向量中添加新项

level 3: 打表寻找递推公式

e.g1:luogu5004 专心OI - 跳房子

本题不难想到二维DP的解法(虽然会爆)

伪代码如下,其中i表示在格子上停留的次数,j表示当前走在第j个格子上

procedure(DP):
int dp[N][N]
int n,m
read n,m
int maxstep = (n+1)/(m+1)

for i:= 1 to n
    dp[1][i] = 1

for i:= 2 to n
    for j:= 1 to n
        int begin = 1
        int end = j-1+m
        for k:= begin to end
            dp[i][j] += dp[i-1][k]

ans = 1
for i:= 1 to maxstep
    for j:= 1 to n
        ans += dp[i][j]

return ans

用这个思路来运行较小的n,m值,打表寻找规律:

  • m=1
n = 0   m = 1   ans = 1
n = 1   m = 1   ans = 2
n = 2   m = 1   ans = 3
n = 3   m = 1   ans = 5
n = 4   m = 1   ans = 8
n = 5   m = 1   ans = 13
n = 6   m = 1   ans = 21
n = 7   m = 1   ans = 34
n = 8   m = 1   ans = 55
n = 9   m = 1   ans = 89
n = 10  m = 1   ans = 144
n = 11  m = 1   ans = 233
n = 12  m = 1   ans = 377
n = 13  m = 1   ans = 610
n = 14  m = 1   ans = 987
n = 15  m = 1   ans = 1597
n = 16  m = 1   ans = 2584
n = 17  m = 1   ans = 4181
n = 18  m = 1   ans = 6765
n = 19  m = 1   ans = 10946
n = 20  m = 1   ans = 17711

可以发现m=1时ans符合斐波那契数列的增长规律

[m = 1 o left{ egin{align} & A_{0} = 1\ & A_{1} = 2\ & A_{2} = 3\ & A_{n} = A_{n-1}+A_{n-2} (n geqslant 3) end{align} ight. ]

  • m = 2
n = 0   m = 2   ans = 1
n = 1   m = 2   ans = 2
n = 2   m = 2   ans = 3
n = 3   m = 2   ans = 4
n = 4   m = 2   ans = 6
n = 5   m = 2   ans = 9
n = 6   m = 2   ans = 13
n = 7   m = 2   ans = 19
n = 8   m = 2   ans = 28
n = 9   m = 2   ans = 41
n = 10  m = 2   ans = 60
n = 11  m = 2   ans = 88
n = 12  m = 2   ans = 129
n = 13  m = 2   ans = 189
n = 14  m = 2   ans = 277
n = 15  m = 2   ans = 406
n = 16  m = 2   ans = 595
n = 17  m = 2   ans = 872
n = 18  m = 2   ans = 1278
n = 19  m = 2   ans = 1873
n = 20  m = 2   ans = 2745

[m = 2 o left{ egin{align} & A_{0} = 1\ & A_{1} = 2\ & A_{2} = 3\ & A_{3} = 4\ & A_{n} = A_{n-1}+A_{n-3} (n geqslant 4) end{align} ight. ]

  • m = 4
n = 0   m = 4   ans = 1
n = 1   m = 4   ans = 2
n = 2   m = 4   ans = 3
n = 3   m = 4   ans = 4
n = 4   m = 4   ans = 5
n = 5   m = 4   ans = 6
n = 6   m = 4   ans = 8
n = 7   m = 4   ans = 11
n = 8   m = 4   ans = 15
n = 9   m = 4   ans = 20
n = 10  m = 4   ans = 26
n = 11  m = 4   ans = 34
n = 12  m = 4   ans = 45
n = 13  m = 4   ans = 60
n = 14  m = 4   ans = 80
n = 15  m = 4   ans = 106
n = 16  m = 4   ans = 140
n = 17  m = 4   ans = 185
n = 18  m = 4   ans = 245
n = 19  m = 4   ans = 325
n = 20  m = 4   ans = 431

[m = 4 o left{ egin{align} & A_{0} = 1\ & A_{1} = 2\ & A_{2} = 3\ & A_{3} = 4\ & A_{4} = 5\ & A_{5} = 6\ & A_{n} = A_{n-1}+A_{n-5} (n geqslant 6) end{align} ight. ]

找到规律

[forall m o left{ egin{align} & A_n = n+1 (0 leqslant n leqslant m+1)\ & A_{n} = A_{n-1}+A_{n-m-1} (n geqslant m+2) end{align} ight. ]

因此有

本题关系式为

[left[ egin{matrix} A_n & A_{n-1} &cdots & A_{n-m} end{matrix} ight]* left[ egin{matrix} 1 & 1 & 0 & 0 &cdots & 0 & 0\ 0 & 0 & 1 & 0 &cdots & 0 & 0\ 0 & 0 & 0 & 1 & cdots & 0&0\ 0 & 0 & 0 & 0 & 1 & cdots &0\ vdots & vdots & vdots & vdots & vdots & ddots & 1& \ 1 & 0 & 0 & 0 & cdots & 0 & 0\ end{matrix} ight] = left[ egin{matrix} A_{n+1} & A_{n} cdots & A_{n-m+1} end{matrix} ight] ]

[R_{1 imes (m+1)} * R_{(m+1) imes(m+1)} = R_{1 imes (m+1)} ]

代码

#include <cstdio>
#include <cstdlib>
using namespace std;

#define N 18+1 
// 最大矩阵规模,根据题目修改

typedef long long ll;
const ll mod = 1e9+7; // 取模

struct Matrix{
    int x;
    int y;
    ll a[N][N];
    Matrix(){};
    Matrix(int m,int n){
        x = m;
        y = n;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                a[i][j] = 0;
            }
        }
    }

    ll* operator[](int x){
        return a[x];
    }

    void ones(){
        for(int i = 1; i <= x; i++){
            a[i][i] = 1;
        }
    }
    Matrix operator *(Matrix b){
        Matrix ans(x,b.y);
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= b.y; j++){
                for(int k = 1; k <= y; k++){
                    ans[i][j] += a[i][k] * b[k][j];
                }
                ans[i][j] %= mod;
            }
        }
        return ans;
    }

    Matrix operator %(ll md){
        Matrix ans = *this;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                ans[i][j] %= md;
            }
        }
        return ans;
    }

    Matrix operator ^(ll p){
        Matrix ans(x,x);
        Matrix base = *this;
        ans.ones();
        while(p){
            if(p&1){
                ans = ans * base;
                ans = ans % mod;
            }
            base = base * base;
            base = base % mod;
            p >>= 1;
        }
        return ans;
    }
};

int dp[N][N];
int main(){
    
    ll n,m;
    scanf("%lld%lld",&n,&m);
    Matrix a(1,m+1);
    a[1][m+1] = 1;
    for(int i = m; i >= 1; i--){
        a[1][i] = a[1][i+1]+1;
    }

    Matrix b(m+1,m+1);
    b[1][1] = 1;
    b[m+1][1] = 1;
    for(int i = 1; i <= m; i++){
        b[i][i+1] = 1;
    }

    if(n < m){
        printf("%lld
",a[1][m+1-n]);
    }else{
        Matrix ans = a * (b^(n-m));
        printf("%lld
",ans[1][1]);
    }
    // system("pause");
    return 0;
}

level4: 数论+DP+矩阵加速

e.g1: P3746 [六省联考2017]组合数问题

本题如果具备组合数递推的前置知识就不算难题,关于组合数的前置知识请看:浅谈组合数相关性质

根据组合数的递推性质

[C_{m}^{n} = C_{m-1}^{n} + C_{m-1}^{n-1} ]

它其实就是DP方程,表示前m个物品选择n个物品的方案数。根据最后一个物品选或不选分别对应出(C_{m-1}^n)(C_{m-1}^{n-1})

写成DP形式即为

[dp[i][j] = dp[i-1][j] + dp[i-1][j-1] ]

本题要求的为

[sum_{i=0}^{+infin}C_{nk}^{nk+r} ]

它所表示的是从nk个物品中选择m个物品,其中m%k = r,求对于所有的m的方案数的总和

我们设(dp[i][j])表示从前i个物品中选择出 m % k = j 个物品的方案数

[dp[i][0] = dp[i-1][0] + dp[i-1][k-1] \ dp[i][j] = dp[i-1][j] + dp[i-1][j-1] ]

因此递推关系式为:

[left[ egin{matrix} 1 & 0 & 0 & 0 & cdots & cdots & 0 end{matrix} ight] * left[ egin{matrix} 1 & 1 & 0 & 0 & 0 & cdots & 0 \ 0 & 1 & 1 & 0 & 0 & cdots & 0 \ 0 & 0 & 1 & 1 & 0 & cdots & 0 \ 0 & 0 & 0 & 1 & 1 & cdots & 0 \ vdots & vdots&vdots& vdots&vdots &cdots & 0 \ vdots & vdots&vdots& vdots&vdots &1 & 0 \ vdots & vdots&vdots& vdots&vdots &1 & 1 \ 1 & 0 & 0 & 0 & 0 & cdots & 1 end{matrix} ight]^{nk} = left[ egin{matrix} f_{nk,0} & f_{nk,1} & f_{nk,2} & cdots & cdots & f_{nk,k-1} end{matrix} ight] ]

最后,k=1需要特判,k=1时应该为

[left[ egin{matrix} 1 end{matrix} ight] * left[ egin{matrix} 2 end{matrix} ight]^{n} = left[ egin{matrix} f_{n,0} end{matrix} ight] ]

最终答案为

ans[1][r+1]; // ans: Matrix(1,k)
#include <cstdio>
#include <cstdlib>

using namespace std;

#define N 50+5 
// 最大矩阵规模,根据题目修改

typedef long long ll;
ll mod; // 取模

struct Matrix{
    int x;
    int y;
    ll a[N][N];
    Matrix(){};
    Matrix(int m,int n){
        x = m;
        y = n;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                a[i][j] = 0;
            }
        }
    }

    ll* operator[](int x){
        return a[x];
    }

    void ones(){
        for(int i = 1; i <= x; i++){
            a[i][i] = 1;
        }
    }
    Matrix operator *(Matrix b){
        Matrix ans(x,b.y);
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= b.y; j++){
                for(int k = 1; k <= y; k++){
                    ans[i][j] += a[i][k] * b[k][j] % mod;
                    ans[i][j] %= mod;
                }
            }
        }
        return ans;
    }

    Matrix operator %(ll md){
        Matrix ans = *this;
        for(int i = 1; i <= x; i++){
            for(int j = 1; j <= y; j++){
                ans[i][j] %= md;
            }
        }
        return ans;
    }

    Matrix operator ^(ll p){
        Matrix ans(x,x);
        Matrix base = *this;
        base = base % mod;
        ans.ones();
        while(p){
            if(p&1){
                ans = ans * base;
                ans = ans % mod;
            }
            base = base * base;
            base = base % mod;
            p >>= 1;
        }
        return ans;
    }
};

int main(){
    ll n,p,k,r;
    scanf("%lld%lld%lld%lld",&n,&p,&k,&r);
    mod = p;
    Matrix a(1,k); // j from 0 to k-1
    a[1][1] = 1; // dp[0][0] = 1
    
    Matrix b(k,k);
    for(int i = 1; i <= k; i++){
        b[i][i] = 1;
        b[i-1][i] = 1;
    } 
    b[k][1] = 1;
    if(k == 1){
        b[1][1] = 2;
    }
    Matrix ans = a * (b^(n*k));
    printf("%lld
",ans[1][r+1]);
    // system("pause");
    return 0;
}

---- suffer now and live the rest of your life as a champion ----
原文地址:https://www.cnblogs.com/popodynasty/p/13798951.html