• 矩阵快速幂小结


    update in 9.17

    矩阵

    并不想扯什么高端线代的内容因为我也不会

    定义

    由$n imes m$个数$a_{ij}$排成的$n$行$m$列的数表称为$n$行$m$列的矩阵,简称$n imes m$矩阵。

    $$
    A =
    egin{bmatrix}
    a_{11} & a_{12} & dots a_{1m} \
    a_{21}, & dots & dots \
    a_{31}, & dots & dots \
    a_{41} & dots & a_{nm}
    end{bmatrix}
    $$

    运算

    这里只讲加法减法和乘法,其他的例如矩阵求逆等与本文内容出入较大,有兴趣的可以自己学习

    加法

    注意,只有行列均相同的矩阵才有加法!

    运算也比较简单,把对应位置的数相加得到一个新的矩阵,即为答案

    例如

    $$
    egin{bmatrix} 1 & 1 & 2 \ 1 & 0 & 1 end{bmatrix}
    +
    egin{bmatrix} 2 & 3 & 3 \ 3 & 3 & 2 end{bmatrix}
    =
    egin{bmatrix} 3 & 4 & 5 \ 4 & 3 & 3 end{bmatrix}
    $$

    加法满足以下运算律

    $A + B = B + A$

    $(A + B) + C = A + (B + C)$

    减法

    与加法同理。

    乘法

    这才是重点!!

    两个矩阵能进行乘法的前提条件是:一个矩阵的行数等于另一个矩阵的列数

    形式化的来说,若$A$是$i imes k$的矩阵,那么$B$必须是$k imes j$的矩阵!

    他们相乘得到的$C$是$i imes j$的矩阵

    其中$C_{ij} = sum_{i = 1}^n A_{ik} * B_{kj}$

    比如

    $$
    egin{bmatrix} 1 & 2\ 2 & 3 end{bmatrix}
    imes
    egin{bmatrix} 2 & 4 & 5 \ 3 & 4 & 3 end{bmatrix}
    =
    egin{bmatrix} 8 & 12 & 11 \ 13 & 20 & 19 end{bmatrix}
    $$

    乘法满足结合律,左分配律,右分配律,即

    $(A imes B) imes C = A imes (B imes C)$

    $(A + B) imes C = A imes C + B imes C$

    $C(A + B) = C imes A + C imes B$

    千万注意!矩阵乘法不满足交换律!(很多情况下交换之后都不能相乘)

    矩阵快速幂

    因为矩阵有结合律,因此我们可以把整数的快速幂推广的矩阵上面

    题目链接

    同样是利用二进制倍增的思想,不难得到以下代码

    其中的base,代表的是单位矩阵,也就是除了对角线全为$1$,其他位置都为$0$的矩阵,可以证明任意矩阵乘单位矩阵都等于自身

    显然矩阵快速幂的复杂度为$O(n^3 log k)$

    #include<cstdio>
    #define LL long long 
    using namespace std;
    const int mod = 1e9 + 7;
    int N;
    LL K;
    struct Matrix {
        int m[101][101];
        Matrix operator * (const Matrix &rhs) const {
            Matrix ans = {};
            for(int k = 1; k <= N; k++) 
                for(int i = 1; i <= N; i++) 
                    for(int j = 1; j <= N; j++) 
                        (ans.m[i][j] += 1ll * m[i][k] * rhs.m[k][j] % mod) %= mod;
            return ans;
        }
    };
    Matrix fastpow(Matrix a, LL p) {
        Matrix base;
        for(int i = 1; i <= N; i++) base.m[i][i] = 1;//构造单位矩阵
        while(p) {
            if(p & 1) base = base * a;
            a = a * a; p >>= 1;
        }
        return base;
    }
    int main() {
        scanf("%d %lld", &N, &K);
        Matrix a; 
        for(int i = 1; i <= N; i++)    
            for(int j = 1; j <= N; j++)
                scanf("%d", &a.m[i][j]);
        a = fastpow(a, K);
        for(int i = 1; i <= N; i++, puts("")) 
            for(int j = 1; j <= N; j++)
                printf("%d ", a.m[i][j]);
        
        return 0;
    }

    应用

    斐波那契数列第$n$项

    矩阵快速幂最常见的应用就是优化递推啦

    还是从最常见的斐波那契数列说起吧。

    众周所知,斐波那契数列的递推公式为$$f_{n} = f_{n - 1} + f_{n - 2}, f_1 = 1, f_2 = 1$$

    一般来说,这种看起来长得很简单,只与自身的函数值有关(可能带几个常数)的式子,一般都可以用矩阵快速幂来加速。

    当然,如果你想找刺激,可以学一下这玩意儿

    矩阵快速幂具体是怎么加速递推的呢?

    首先我们把斐波那契数列写成矩阵的形式,因为$f_n$的取值与$f_{n - 1}, f_{n - 2}$这两项有关,因此我们需要同时保留这两项的值,我们不难得到一个$2 imes 1$的矩阵

    $$
    egin{bmatrix}
    f_{n} \
    f_{n - 1}
    end{bmatrix}
    $$

    现在我们要进行递推,也就是得到这样一个矩阵

    $$
    egin{bmatrix}
    f_{n + 1} \
    f_{n}
    end{bmatrix}
    $$

    展开

    $$
    egin{bmatrix}
    f_{n} + f_{n - 1} \
    f_{n}
    end{bmatrix}
    $$

    观察一下,上面的一项需要用到$f_{n}$和$f_{n - 1}$,下面的一项只需要用到$f_n$

    同时结合上面的矩阵乘法的定义,我们不难得到一个转移矩阵

    $$
    egin{bmatrix} 1 & 1 \ 1 & 0 \ end{bmatrix}
    egin{bmatrix} f_{n} \ f_{n - 1}\ end{bmatrix}
    =
    egin{bmatrix} f_{n} + f_{n - 1} \ f_{n}\ end{bmatrix}
    $$

    这样我们乘一次即可递推到下一项。

    但是这样好像并没有什么卵用啊,复杂度上还多了个矩阵相乘

    嘿嘿

    不要忘了,我们前面可以讲过矩阵有结合律的!

    这样的话我们只需要把转移矩阵自乘$n - 1$次,然后再与初始矩阵相乘,就能得到答案矩阵啦!

    题目链接

    // luogu-judger-enable-o2
    #include<cstdio>
    #include<cstring>
    #define LL long long 
    using namespace std;
    const int mod = 1e9 + 7;
    LL K;
    struct Matrix {
        int m[101][101], N;
        Matrix() {
            memset(m, 0, sizeof(m));
            N = 2;
        }
        Matrix operator * (const Matrix &rhs) const {
            Matrix ans;
            for(int k = 1; k <= N; k++) 
                for(int i = 1; i <= N; i++) 
                    for(int j = 1; j <= N; j++) 
                        (ans.m[i][j] += 1ll * m[i][k] * rhs.m[k][j] % mod) %= mod;
            return ans;
        }
    };
    Matrix fastpow(Matrix a, LL p) {
        Matrix base; 
        for(int i = 1; i <= base.N; i++) base.m[i][i] = 1;//鏋勯€犲崟浣嶇煩闃?
        while(p) {
            if(p & 1) base = base * a;
            a = a * a; p >>= 1;
        }
        return base;
    }
    int main() {
        scanf("%lld", &K);
        Matrix a;
        a.m[1][1] = 1; a.m[1][2] = 1;
        a.m[2][1] = 1; a.m[2][2] = 0;
        a = fastpow(a, K - 1);
        printf("%d", a.m[1][1]);
        return 0;
    }
    代码

    路径计数问题

    https://www.nowcoder.com/acm/contest/185/B

    给出一个 n * n 的邻接矩阵A.
    A是一个01矩阵 .
    A[i][j]=1表示i号点和j号点之间有长度为1的边直接相连.
    求出从 1 号点 到 n 号点长度为k的路径的数目.

    做法:直接对给出的矩阵快速幂$k$次,输出$A[1][N]$

    解释:考虑矩阵相乘的过程,我们对于要计算的$(i, j)$位置,我们相当于是枚举了一个中间节点$k$,来计算$(i, k) * (k, j)$

    其他的常见矩阵推导

    $$G_i = a imes G_{i - 1} + b imes G_{i - 2}$$

    egin{equation*}
    egin{bmatrix}
    a&b\
    1 & 0\
    end{bmatrix}^{i - 1} imes
    egin{bmatrix}
    G_{1}\
    G_{0}\
    end{bmatrix}=
    egin{bmatrix}
    a&b\
    1 & 0\
    end{bmatrix} imes
    egin{bmatrix}
    G_{i - 1}\
    G_{i - 2}\
    end{bmatrix}=
    egin{bmatrix}
    G_{i}\
    G_{i - 1}\
    end{bmatrix}
    end{equation*}

    $$G_i = a imes G_{i - 1} + i^2$$

    egin{equation*}
    egin{bmatrix}
    a&1&0&0\
    0 & 1&2&1\
    0 & 0&1&1\
    0 & 0&0&1\
    end{bmatrix}^{i} imes
    egin{bmatrix}
    G_{0}\
    1\
    1\
    1\
    end{bmatrix}=
    egin{bmatrix}
    a&1&0&0\
    0 & 1&2&1\
    0 & 0&1&1\
    0 & 0&0&1\
    end{bmatrix} imes
    egin{bmatrix}
    G_{i - 1}\
    i^2\
    i\
    1
    end{bmatrix}=
    egin{bmatrix}
    G_{i}\
    (i + 1)^2\
    i + 1\
    1
    end{bmatrix}
    end{equation*}

    $$G_i = a imes G_{i - 1} + i^3$$

    egin{equation*}
    egin{bmatrix}
    a&1&0&0&0\
    0 & 1&3&3&1\
    0 & 0&1&2&1\
    0 & 0&0&1&1\
    0 & 0&0&0&1\
    end{bmatrix}^{i}*
    egin{bmatrix}
    G_{0}\
    1\
    1\
    1\
    1\
    end{bmatrix}=
    egin{bmatrix}
    a&1&0&0&0\
    0 & 1&3&3&1\
    0 & 0&1&2&1\
    0 & 0&0&1&1\
    0 & 0&0&0&1\
    end{bmatrix} imes
    egin{bmatrix}
    G_{i - 1}\
    i^3\
    i^2\
    i\
    1
    end{bmatrix}=
    egin{bmatrix}
    G_{i}\
    (i + 1)^3\
    (i + 1)^2\
    i + 1\
    1
    end{bmatrix}
    end{equation*}

    $$G_i = a imes G_{i - 1} + b^i$$

    egin{equation*}
    egin{bmatrix}
    a&1\
    0 & b\
    end{bmatrix}^{i} imes
    egin{bmatrix}
    G_{0}\
    b\
    end{bmatrix}=
    egin{bmatrix}
    a&1\
    0 & b\
    end{bmatrix} imes
    egin{bmatrix}
    G_{i - 1}\
    b^{i}\
    end{bmatrix}=
    egin{bmatrix}
    G_{i}\
    b^{i+1}\
    end{bmatrix}
    end{equation*}

    参考资料

    深入浅出矩阵快速幂及其简单应用

     
     
  • 相关阅读:
    02 createJS中关于图片优化的问题(推荐一个图片优化的网站)
    01 createJS代码作用域
    文件读取一些payload
    SQL注入笔记-updatexml与extractvalue
    四位数六位数字典生成
    XShell上传文件到Linux服务器上
    centos彻底删除文件夹创建文件
    2020.6.18 时间记录 总学习5.15
    2020.6.14 时间记录
    五种IT职位的介绍
  • 原文地址:https://www.cnblogs.com/zwfymqz/p/9642901.html
Copyright © 2020-2023  润新知