In solitute,where we are |

Luisvacson

园龄:3年6个月粉丝:5关注:0

浅谈矩阵乘法

1.矩阵乘法

给出两个矩阵A,B,令CA×B的结果,若A是一个n×m的矩阵,B是一个m×p的矩阵,则C是一个n×p的矩阵。其中有:

i[1,n],j[1,p],Ci,j=k=1mAi,k×Bk,j

注意到由于矩阵乘法的定义,只有当A的列数等于B的行数时才可以相乘,否则无意义。

柿子的定义可能比较抽象,难以理解,那我们就举个例子:

[123456][123456]

根据柿子手摸可以得到结果:

[22284964]

形象化地说,Ci,j就是把A的第i行和B的第j列的m个数一个个乘起来再相加。

参考代码:

#include <bits/stdc++.h>
using namespace std;

class Matrix {
   public:
    int n, m;
    int val[55][55];
    Matrix() { memset(val, 0, sizeof(val)); }
    void init() {
        for (int i = 1; i <= 50; ++i) {
            for (int j = 1; j <= 50; ++j) {
                val[i][j] = 1;
            }
        }
    }
};

Matrix operator*(const Matrix &a, const Matrix &b) {
    int i, j, k;
    Matrix res;
    for (k = 1; k <= a.m; ++k) {
        for (i = 1; i <= a.n; ++i) {
            for (j = 1; j <= b.m; ++j) {
                res.val[i][j] += a.val[i][k] * b.val[k][j];
            }
        }
    }

    return res;
}

signed main() {
    Matrix a, b, ans;
    int n, m, p;
    int i, j;

    scanf("%d%d%d", &n, &m, &p);
    a.n = n, a.m = m;
    b.n = m, b.m = p;
    for (i = 1; i <= n; ++i) {
        for (j = 1; j <= m; ++j) {
            scanf("%d", &a.val[i][j]);
        }
    }

    for (i = 1; i <= m; ++i) {
        for (j = 1; j <= p; ++j) {
            scanf("%d", &b.val[i][j]);
        }
    }

    ans = a * b;
    for (i = 1; i <= n; ++i) {
        for (j = 1; j <= p; ++j) {
            printf("%d\n", ans.val[i][j]);
        }
    }
    return 0;
}

2.矩阵快速幂

矩阵乘法满足结合律,即对于矩阵A,B,C,有

(A×B)×C=A×(B×C)

所以我们可以对于矩阵进行经典快速幂运算。

这里要注意,矩阵乘法不满足交换律,即A×BB×A

洛谷P3390【模板】矩阵快速幂

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int p = 1e9 + 7;
#define int long long

inline int read()
{
    int ans = 0, f = 1;
    char ch = getchar();
    while (ch > '9' || ch < '0') {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        ans = (ans << 1) + (ans << 3) + (ch ^ '0');
        ch = getchar();
    }
    return ans * f;
}

int n, k;

struct Matrix {
    int a[105][105];
    Matrix()
    {
        memset(a, 0, sizeof a);
    }
    inline void init()
    {
        for (int i = 1; i <= n; ++i)
            a[i][i] = 1;
    }
} t, ans;

Matrix operator*(const Matrix& x, const Matrix& y)
{
    Matrix z;
    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                z.a[i][j] = (z.a[i][j] + x.a[i][k] * y.a[k][j] % p) % p;
    return z;
}

Matrix power(Matrix a, int b)
{
    Matrix ans, base = a;
    ans.init();
    while (b) {
        if (b & 1)
            ans = ans * base;
        base = base * base;
        b >>= 1;
    }
    return ans;
}

signed main()
{
    n = read(), k = read();
    register int i, j;
    ans.init();

    for (i = 1; i <= n; ++i) {
        for (j = 1; j <= n; ++j) {
            t.a[i][j] = read();
        }
    }
    ans = power(t, k);

    for (i = 1; i <= n; ++i) {
        for (j = 1; j <= n; ++j)
            printf("%lld ", ans.a[i][j]);
        puts("");
    }

    return 0;
}

3.应用

我们来看一个经典例题:

给出n,求斐波那契数列的第n项mod 998244353
1n109

暴力递推显然满足不了109的数据范围,所以我们需要加速这个过程。

加速的方法很多,比如特征方程求通项公式等数学方法;这里给出矩阵快速幂的解法。

我们构造一个矩阵:

[fifi1]

其中fi表示斐波那契数列的第i项。

我们把这个矩阵乘上一个base矩阵:

[1110]

得到:

[fi×1+fi1×1fi×1+fi1×0]

即:

[fi+1fi]

发现什么?我们通过一次矩阵乘法的运算即得到了斐波那契数列的下一项,完成了一次递推。同时,矩阵可以进行快速幂,所以我们可以通过乘上base矩阵的n次幂来快速递推,复杂度被优化到了O(logn)

到这里可以发现,矩阵快速幂可以加速递推式的计算过程。

我们再次观察上述求解过程:到底是怎么构造出原矩阵和base矩阵的呢?

斐波那契数列的通项公式为:fi=fi1+fi2,所以我们要知道fi+1必须要知道fifi1。因为矩阵乘法是两个矩阵内部的运算,不可能凭空出现一个新的值,所以我们肯定要在原矩阵里面记录下这两个值才能得到fi+1

所以现在我们有矩阵[fifi1],要乘上一个base矩阵之后得到[fi+1fi],又

fi+1=fi×1+fi1×1fi=fi×1+fi1×0

将系数联立起来即可得到base矩阵。

4.例题

1.洛谷P1939【模板】矩阵加速(数列)

给出n,已知fi=fi1+fi3,求fn
1n109

我们构造矩阵[fifi1fi2],希望得到结果为[fi+1fifi1]

显然有:

fi+1=fi×1+fi1×0+fi2×1fi=fi×1+fi1×0+fi2×0fi1=fi×0+fi1×1+fi2×0

所以构造出base矩阵:

[110001100]

2.[NOI2012] 随机数生成器

给出n,a,c,已知fi=afi1+c,求fn
1n1018

这里发现fi只与fi1有关,所以我们构造出原始矩阵:

[fic]

显然有:

fi+1=a×fi+1×cc=0×fi+1×c

所以我们乘上:

[a011]

即可得到:

[fi+1c]

这道题目原题好像裸的矩阵快速幂会炸longlong要加上龟速乘,不过这跟无辜的矩阵有什么关系呢

3.LOJ#10222 佳佳的 Fibonacci

fi为斐波那契数列的第i项,gi=j=1ifj×j,求gn
1n2311

我们发现

gi=gi1+i×fifi=fi1+fi2

所以我们可以根据这个递推式构造矩阵:

[giifi(i1)fi1fifi1]

因为

gi+1=gi+(i+1)fi+1(i+1)fi+1=ifi+fi+(i1)fi1+2fi1ifi=ififi+1=fi+fi1fi=fi

所以我们得到base矩阵为

[1000011100110001101122010]

将原矩阵乘上base矩阵即可得到:

[gi+1(i+1)fi+1ififi+1fi]

4.[HNOI2011]数学作业

定义fi为从1i的所有整数写在一起得到的数,如f7=1234567,f13=12345678910111213,求fnmodm
1n1018

显然有fi=fi1×10len(i)+i,其中len(i)表示i的位数,所以可以就此进行矩阵加速递推。

原矩阵:

[fii1]

base矩阵:

[10len(i+1)00110111]

但是我们发现len(i)不好维护,考虑到1n1018,所以len的值只有18种左右,枚举位数分开矩乘即可。

5.[SNOI2017]礼物

f1=1,si=j=1ifj,fi=si1+ik,求fn
1n1018,1k10

先推推柿子:

si=si1+fi=2si1+ik

这样我们得到了s的递推式,尝试矩阵优化;但是我们发现不好直接维护ik,考虑二项式定理:

(x+1)k=i=0k(ki)xi

于是便可以构造矩阵:

[siikik1i0]

我们希望得到

[si+1(i+1)k(i+1)k1(i+1)0]

于是便可以推出base矩阵:

[200000(k0)(k0)0000(k1)(k1)(k10)000(k2)(k2)(k11)(k20)00(kk)(kk)(k1k1)(k2k2)(k3k3)(00)]

这里定义(00)=1

注意到k非常小,所以以上做法可以通过。

6.[THUSCH2017] 大魔法师

给出n个元素,每个元素有3个值:A,B,C,共进行m次操作,每次选取一个区间[l,r]进行如下几种操作之一:
1.AiAi+Bi
2.BiBi+Ci
3.CiCi+Ai
4.AiAi+v
5.BiBi×v
6.Civ
7.i=lrAi,i=lrBi,i=lrCi
请对于每个操作7输出结果
1n,m2.5×105

我们把每个元素看成一个矩阵:

[AiBiCi1]

显然每次操作可以用矩阵乘法来表示,例如操作1base为:

[1000110000100001]

操作26同理

我们对于这n个矩阵开一棵线段树,显然操作16即为区间乘,操作7为区间求和,线段树都可以完成。

注意到矩阵存在分配律,所以可以用懒标记实现。

posted @   Luisvacson  阅读(399)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起