zoukankan      html  css  js  c++  java
  • 矩阵乘法与矩阵加速

    矩阵乘法与矩阵加速

    矩阵乘法

    矩阵乘法比较简单,就是两个矩阵相乘得到一个新矩阵的运算.

    乘法的过程就是:

    第一个矩阵的每一行和第二个矩阵的每一列对应位置相乘相加,放入新矩阵.

    不太显然,矩阵乘法对于参与运算的矩阵是有限制的:

    [[n imes m] * [m imes k] = [n imes k] ]

    即,一个 (n imes m) 的矩阵和一个 (m imes k) 的矩阵相乘得到一个 (n imes k) 的矩阵.

    必须是形如上面那样的矩阵才能进行乘法.

    从这里可以看出,一般的矩阵乘法是不满足交换律的.(个别情况除外)

    显然,一次矩阵乘法的复杂度是 (Theta(n imes m imes k)) 的.

    两个矩阵相乘的代码如下:

    struct Matrix {
        LL e[N][M] , line , row ;
    
        inline void clear () { line = row = 0 ; MEM ( e , 0 ) ; return ; }
    
        friend Matrix operator * (Matrix a , Matrix b) {
            Matrix c ; c.clear () ; c.line = a.line ; c.row = b.row ;
            rep ( k , 0 , a.row - 1 ) rep ( i , 0 , a.line - 1 ) rep ( j , 0 , b.row - 1 )
                c.e[i][j] = ( c.e[i][j] + a.e[i][k] * b.e[k][j] % mod ) % mod ;
            return c ;
        }
    } ;
    

    上面的代码采用了结构体的封装形式,用起来更方便.

    矩阵快速幂

    有了矩阵乘法,我们再来考虑和乘法密切相关的一种运算:幂运算.

    即一个矩阵的若干次幂如何计算,显然,只有正方形的矩阵可以计算高次幂.

    那么类比于实数幂次,一个矩阵的 (p) 次幂只需要把它自乘 (p) 次即可.

    讨论到了幂运算的问题,就不得不提快速幂.

    众所周知,快速幂的复杂度是 (Theta(log_2{P})) 的,其中 (P) 是模数.

    这样的复杂度实在令人眼馋,相比于自乘若干次快了不知道多少倍.

    那矩阵的幂次也可以像快速幂那样分治处理吗?

    我们知道矩阵乘法是不满足交换律的,那么它满足结合律吗?

    如果它满足结合律,那么它就可以像快速幂那样运算.

    事实上,矩阵乘法是满足结合律的.

    于是矩阵快速幂的代码也出炉了:

    inline Matrix quick (Matrix a , LL p) {
        Matrix c ; c.clear () ; c.line = a.line ; c.row = a.row ;
        rep ( i , 0 , c.line - 1 ) c.e[i][i] = 1 ;
        while ( p ) {
            if ( p & 1 ) c = c * a ;
            a = a * a ; p >>= 1 ;
        }
        return c ;
    }
    

    十分简单!

    矩阵加速

    根据一些资料,我们知道矩乘的本质是高维向量卷积,方程代换和线性变换.

    由此得到启发,能否用矩阵来转移递推方程? 答案是肯定的.

    (Fibonacci) 数列的递推为例,我们来考虑构造转移矩阵.

    众所周知, (Fibonacci) 数列的递推式是 (f_n=f_{n-1}+f_{n-2}).

    可以发现, (Fibonacci) 的递推只和某一项的前两项相关.

    所以我们考虑的矩阵应该是 (2 imes 2) 的.

    我们的初始矩阵是这样的:

    [left[egin{array}{ll}{f_i}&{f_{i-1}}end{array} ight] ]

    而目标矩阵是这样的:

    [left[egin{array}{ll}{f_{i+1}}&{f_{i}}end{array} ight] ]

    所以转移矩阵长这样:

    [left[egin{array}{ll}{a} & {b} \ {c} & {d} end{array} ight] ]

    我们要的矩阵转移式就是这样的:

    [left[egin{array}{ll}{f_{i}} \ {f_{i-1}} end{array} ight] imes left[egin{array}{ll}{a} & {b} \ {c} & {d} end{array} ight] = left[egin{array}{l}{f_{i+1}} \ {f_{i}}end{array} ight] ]

    根据矩阵乘法的过程,可以得到:

    [a imes f_i + c imes f_{i-1} = f_{i+1} ]

    [b imes f_i + d imes f_{i-1} = f_{i} ]

    显然,(a=1,c=1,b=1,d=0).

    于是,转移矩阵为:

    [left[egin{array}{ll}{1} & {1} \ {1} & {0} end{array} ight] ]

    初始矩阵(emmmm...),不就是这个嘛

    [left[egin{array}{l} {1} & {1} end{array} ight] ]

    一次转移就乘一次转移矩阵,自行判断幂次即可.

    矩阵加速递推的扩展

    (updated:)

    扩展情况并不多,一般只是两个递推的组合,常数项的累加以及前缀和,其实本质都是递推组合.

    就都以 (Fibonacci) 数列为例好了.

    扩展 1 带常数项

    定义数列 (F_n) 的递推式为 (F_n=F_{n-1}+F_{n-2}+1) , 其中 (F_1 = F_2 = 1).

    没有常数项的部分就是一个普通的 (Fibonacci) 数列.

    我们令 (g_n) 表示常数项的递推式,显然, (g_n=g_{n-1}).

    于是我们的初始矩阵就是 (:)

    [left[egin{array}{llll} F_{n} & F_{n-1} & g_n end{array} ight] ]

    目标矩阵就是 (:)

    [left[egin{array}{llll} F_{n+1} & F_n & g_{n+1}end{array} ight] ]

    那么我们要的转移矩阵应该满足 (:)

    [left[egin{array}{llll} F_{n} \ F_{n-1} \ g_n end{array} ight] imes left[egin{array}{llll} ? & ? & ? \ ? & ? & ? \ ? & ? & ? end{array} ight] = left[egin{array}{llll} F_{n+1} \ F_n \ g_{n+1}end{array} ight] ]

    根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 (:)

    [left[egin{array}{llll} 1 & 1 & 0 \ 1 & 0 & 0 \ 1 & 0 & 1 end{array} ight] ]

    (因为这些扩展的意义就在于构造不同的矩阵,所以只讲如何构造矩阵).

    扩展 2 带未知项递推

    定义数列 (F_n) 的递推式为 (F_n=F_{n-1}+F_{n-2}+n) , 其中 (F_1=F_2=1).

    没有常数项的部分就是一个普通的 (Fibonacci) 数列.

    (g_n) 表示未知项的递推式,显然, (g_n=g_{n-1}+1).

    于是我们的初始矩阵就是 (:)

    [left[egin{array}{llll} F_{n} & F_{n-1} & g_n & 1 end{array} ight] ]

    目标矩阵就是 (:)

    [left[egin{array}{llll} F_{n+1} & F_n & g_{n+1} & 1 end{array} ight] ]

    那么我们要的转移矩阵应该满足 (:)

    [left[egin{array}{llll} F_{n} \ F_{n-1} \ g_n \ 1 end{array} ight] imes left[egin{array}{llll} ? & ? & ? & ? \ ? & ? & ? & ? \ ? & ? & ? & ? \ ? & ? & ? & ? end{array} ight] = left[egin{array}{llll} F_{n+1} \ F_n \ g_{n+1} \ 1 end{array} ight] ]

    根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 (:)

    [left[egin{array}{llll} 1 & 1 & 0 & 0 \ 1 & 0 & 0 & 0 \ 1 & 0 & 1 & 0 \ 1 & 0 & 1 & 1 end{array} ight] ]

    扩展 3 递推式的组合

    令数列 (F_n) 的递推式为 (F_n=F_{n-1}+F_{n-2}+f_{n-1}+f_{n-2}),其中,(F_1=F_2=f_1=f_2=1).

    这叫啥?双重 (Fibonacci) 数列?算了,还是不乱叫了.

    于是我们的初始矩阵就是 (:)

    [left[egin{array}{llll} F_{n} & F_{n-1} & f_n & f_{n-1} end{array} ight] ]

    目标矩阵就是 (:)

    [left[egin{array}{llll} F_{n+1} & F_n & f_{n+1} & f_n end{array} ight] ]

    那么我们要的转移矩阵应该满足 (:)

    [left[egin{array}{llll} F_{n} \ F_{n-1} \ f_n \ f_{n-1} end{array} ight] imes left[egin{array}{llll} ? & ? & ? & ? \ ? & ? & ? & ? \ ? & ? & ? & ? \ ? & ? & ? & ? end{array} ight] = left[egin{array}{llll} F_{n+1} \ F_n \ f_{n+1} \ f_n end{array} ight] ]

    根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 (:)

    [left[egin{array}{llll} 1 & 1 & 0 & 0 \ 1 & 0 & 0 & 0 \ 1 & 0 & 1 & 1 \ 1 & 0 & 1 & 0 end{array} ight] ]

    扩展 4 前缀和

    令数列 (F_n) 的递推式为 (F_n=F_{n-1}+F_{n-2}) , 求 (sum_{i=1}^{n}{F_i}) , 其中 (F_1=F_2=1).

    (S_i) 表示到 (i) 的前缀和.

    则显然有(:)

    [S_i=F_{i-1}+F_{i-2}+S_{i-1} ]

    于是我们的初始矩阵就是 (:)

    [left[egin{array}{llll} F_{n} & F_{n-1} & S_n end{array} ight] ]

    目标矩阵就是 (:)

    [left[egin{array}{llll} F_{n+1} & F_n & S_{n+1} end{array} ight] ]

    那么我们要的转移矩阵应该满足 (:)

    [left[egin{array}{llll} F_{n} \ F_{n-1} \ S_nend{array} ight] imes left[egin{array}{llll} ? & ? & ? \ ? & ? & ? \ ? & ? & ? end{array} ight] = left[egin{array}{llll} F_{n+1} \ F_n \ S_{n+1} end{array} ight] ]

    根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 (:)

    [left[egin{array}{llll} 1 & 1 & 1 \ 1 & 0 & 1 \ 0 & 0 & 1 end{array} ight] ]

    扩展 5 带系数

    令数列 (F_n) 的递推式为 (F_n = a imes F_{n-1} + b imes F_{n-2}),其中 (F_1,F_2) 是给定的数.

    初始矩阵仍然是 (:)

    [left[egin{array}{llll} F_n & F_{n-1} end{array} ight] ]

    目标矩阵仍然是 (:)

    [left[egin{array}{llll} F_{n+1} & F_{n} end{array} ight] ]

    那么我们要的转移矩阵应该满足 (:)

    [left[egin{array}{llll} F_{n} \ F_{n-1} end{array} ight] imes left[egin{array}{llll} ? & ? \ ? & ? end{array} ight] = left[egin{array}{llll} F_{n+1} \ F_n end{array} ight] ]

    根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 (:)

    [left[egin{array}{llll} a & 1 \ b & 0 end{array} ight] ]

    完结撒花 (!)

    你说为什么这几种扩展的框架一模一样?因为我复制的啊...(懒

    关于矩乘更有意思的应用,我还有另一篇博文矩阵乘法与邻接矩阵

  • 相关阅读:
    二叉排序树(B-Tree)-c实现
    队列(Queue)-c实现
    栈(stack)--c实现(使用双链表)
    链表(list)--c实现
    c 和 指针读书笔记(1)
    c traps and pitfalls reading notes(2)
    js 控制
    正则表达式
    Android 笔记
    Android 布局方式学习
  • 原文地址:https://www.cnblogs.com/Equinox-Flower/p/11672480.html
Copyright © 2011-2022 走看看