首页 > 其他分享 >【精选】矩阵加速

【精选】矩阵加速

时间:2024-06-07 19:33:42浏览次数:29  
标签:begin end 矩阵 精选 cdots pmatrix 加速 vdots

大家好,我是Weekoder!

今天要讲的内容是矩阵加速!

这时候就有人说了:

\(\tiny{\texttt{Weekoder 这么蒻,怎么会矩阵啊。还给我们讲,真是十恶不赦!}}\)

不不不,容我解释。在经过我的研究后,我发现基本的矩阵运算和矩阵加速都并没有那么难。只要继续往下看,相信你也能学会!

注意:以下内容的学习难度将会用颜色表示,与洛谷题目难度顺序一致,即 \(\color{#FE4C61}\texttt{红}\color{#000000}<\color{#F39C11}\texttt{橙}\color{#000000}<\color{#FFC116}\texttt{黄}\color{#000000}<\color{#52C41A}\texttt{绿}\color{#000000}\)。(并不对标洛谷题目难度,只作为学习难易度参考)

\[\huge\texttt{Part 1}\small\texttt{ Definition} \]

\[\color{#FE4C61}\texttt{定义} \]

矩阵和二维数组很像,是由 \(m\times n\) 个数排列成 \(m\) 行 \(n\) 列的一张表,由于排列出来的表是一个矩形,故称其为矩阵。矩阵长这个样子:

\[\begin{pmatrix} a_{11} & a_{12} & a_{13} & \cdots & a_{1n} \\ a_{21} & a_{22} & a_{23} & \cdots & a_{2n} \\ a_{31} & a_{32} & a_{33} & \cdots & a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & a_{m3} & \cdots & a_{mn} \end{pmatrix} \]

可以看到,矩阵中的每个元素都有着对应的行和列,我们把一个矩阵记作 \(A\),第 \(i\) 行 \(j\) 列的元素即为 \(a_{ij}\)。更形式化的,写作:

\[A=(a_{ij})\in \mathbb{F^{m\times n}} \]

其中 \(\mathbb{F}\) 为数域,一般取为实数域 \(\mathbb{R}\) 或复数域 \(\mathbb{C}\)。(看不懂没事,蒟蒻自行走开QWQ)

\[\huge\texttt{Part 2}\small\texttt{ Special matrices} \]

\[\color{#FE4C61}\texttt{特殊矩阵} \]

\(\texttt{1.零矩阵}\)

元素全部为 \(0\) 的矩阵称为零矩阵。像这样:

\[\begin{pmatrix} 0 & 0 & \cdots & 0 \\ 0 & 0 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 0 \end{pmatrix} \]

零矩阵记作 \(0_{m\times n}\),就是在 \(0\) 下面加上矩阵的大小 \(m\times n\)。你可以把零矩阵看做数字 \(0\),任何数乘以 \(0\) 都得 \(0\)。

\(\texttt{2.对角矩阵}\)

只有主对角线上的元素有值,其余元素为 \(0\) 的矩阵称为对角矩阵

注:主对角线为矩阵中从左上角到右下角的一条对角线。

\[\begin{pmatrix} a_1 & 0 & \cdots & 0 \\ 0 & a_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & a_n \end{pmatrix} \]

对角矩阵根据主对角线上的值,记作 \(\text{diag(}\text{a}_1,\text{a}_2,\ldots,\text{a}_n\text{)}\)。

\(\texttt{3.单位矩阵}\)

主对角线上的元素均为 \(1\),其余元素为 \(0\) 的矩阵称为单位矩阵

\[\begin{pmatrix} 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 1 \end{pmatrix} \]

单位矩阵记作 \(I\)。

记得分数中的概念分数单位吗?矩阵单位和分数单位的“地位”差不多,代表的都是最基础的,最小的独立个体。你可以把单位矩阵看做数字 \(1\),任何数乘以 \(1\) 都等于它本身。

最基础的,常见的特殊矩阵就是这些了。当然,还有很多的特殊矩阵,不过我们暂时用不到。

\[\huge\texttt{Part 3}\small\texttt{ Matrix operations} \]

\[\color{#F39C11}\texttt{矩阵运算} \]

\(\texttt{1.相等}\)

若对于矩阵 \(A,B\),所有的 \(i,j\) 都有 \(a_{ij}=b_{ij}\) 且矩阵的行和列相等,则称矩阵 \(A,B\) 相等。

其实就是两个矩阵长得一模一样

\[\begin{pmatrix} a_{11} & a_{12} & a_{13} & \cdots & a_{1n} \\ a_{21} & a_{22} & a_{23} & \cdots & a_{2n} \\ a_{31} & a_{32} & a_{33} & \cdots & a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & a_{m3} & \cdots & a_{mn} \end{pmatrix} = \begin{pmatrix} b_{11} & b_{12} & b_{13} & \cdots & b_{1n} \\ b_{21} & b_{22} & b_{23} & \cdots & b_{2n} \\ b_{31} & b_{32} & b_{33} & \cdots & b_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ b_{m1} & b_{m2} & b_{m3} & \cdots & b_{mn} \end{pmatrix} \]

\(\texttt{2.矩阵加(减)法}\)

若要求 \(A,B\) 两个矩阵之和,即 \(C=A+B\),则对于任意 \(i,j\),满足 \(c_{ij}=a_{ij}+b_{ij}\)。要求矩阵行列相等。

总结一句话:对应位置相加。

\[\begin{aligned} {} & \begin{pmatrix} a_{11} & a_{12} & a_{13} & \cdots & a_{1n} \\ a_{21} & a_{22} & a_{23} & \cdots & a_{2n} \\ a_{31} & a_{32} & a_{33} & \cdots & a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & a_{m3} & \cdots & a_{mn} \end{pmatrix} + \begin{pmatrix} b_{11} & b_{12} & b_{13} & \cdots & b_{1n} \\ b_{21} & b_{22} & b_{23} & \cdots & b_{2n} \\ b_{31} & b_{32} & b_{33} & \cdots & b_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ b_{m1} & b_{m2} & b_{m3} & \cdots & b_{mn} \end{pmatrix} \\ = & \begin{pmatrix} a_{11}+b_{11} & a_{12}+b_{12} & a_{13}+b_{13} & \cdots & a_{1n}+b_{1n} \\ a_{21}+b_{21} & a_{22}+b_{22} & a_{23}+b_{23} & \cdots & a_{2n}+b_{2n} \\ a_{31}+b_{31} & a_{32}+b_{32} & a_{33}+b_{33} & \cdots & a_{3n}+b_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1}+b_{m1} & a_{m2}+b_{m2} & a_{m3}+b_{m3} & \cdots & a_{mn}+b_{mn} \end{pmatrix} \end{aligned} \]

矩阵加法满足交换律和结合律:

\[A+B=B+A \]

\[(A+B)+C=A+(B+C) \]

减法同理,对应位置相减。

\[\begin{aligned} {} & \begin{pmatrix} a_{11} & a_{12} & a_{13} & \cdots & a_{1n} \\ a_{21} & a_{22} & a_{23} & \cdots & a_{2n} \\ a_{31} & a_{32} & a_{33} & \cdots & a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & a_{m3} & \cdots & a_{mn} \end{pmatrix} - \begin{pmatrix} b_{11} & b_{12} & b_{13} & \cdots & b_{1n} \\ b_{21} & b_{22} & b_{23} & \cdots & b_{2n} \\ b_{31} & b_{32} & b_{33} & \cdots & b_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ b_{m1} & b_{m2} & b_{m3} & \cdots & b_{mn} \end{pmatrix} \\ = & \begin{pmatrix} a_{11}-b_{11} & a_{12}-b_{12} & a_{13}-b_{13} & \cdots & a_{1n}-b_{1n} \\ a_{21}-b_{21} & a_{22}-b_{22} & a_{23}-b_{23} & \cdots & a_{2n}-b_{2n} \\ a_{31}-b_{31} & a_{32}-b_{32} & a_{33}-b_{33} & \cdots & a_{3n}-b_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1}-b_{m1} & a_{m2}-b_{m2} & a_{m3}-b_{m3} & \cdots & a_{mn}-b_{mn} \end{pmatrix} \end{aligned} \]

\(\texttt{3.矩阵数乘}\)

数 \(\lambda\)(一个数字) 乘以矩阵 \(A\),记作 \(\lambda A\),即为矩阵数乘运算。若有 \(B=\lambda A\),则对于任意 \(i,j\) 都满足 \(b_{ij}=\lambda a_{ij}\)。

还是一句话:对应位置相乘。

\[\lambda\begin{aligned} {} & \begin{pmatrix} a_{11} & a_{12} & a_{13} & \cdots & a_{1n} \\ a_{21} & a_{22} & a_{23} & \cdots & a_{2n} \\ a_{31} & a_{32} & a_{33} & \cdots & a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & a_{m3} & \cdots & a_{mn} \end{pmatrix} = \begin{pmatrix} \lambda a_{11} & \lambda a_{12} & \lambda a_{13} & \cdots & \lambda a_{1n} \\ \lambda a_{21} & \lambda a_{22} & \lambda a_{23} & \cdots & \lambda a_{2n} \\ \lambda a_{31} & \lambda a_{32} & \lambda a_{33} & \cdots & \lambda a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ \lambda a_{m1} & \lambda a_{m2} & \lambda a_{m3} & \cdots & \lambda a_{mn} \end{pmatrix} \end{aligned} \]

\[\huge\texttt{Part 3.5}\small\texttt{ Matrix multiplication} \]

\[\color{#FFC116}\texttt{矩阵乘法} \]

虽然矩阵乘法也属于矩阵运算,但难度比前面的都高,而且是今天的重点内容,所以单独放出来讲,故记为 \(\texttt{Part 3.5}\)。(话说你们没有发现难度变成黄了吗)

例题!(虽然难度是橙)

先看矩阵乘法的定义:若有 \(n\) 行 \(m\) 列矩阵 \(A\) 和 \(m\) 行 \(k\) 列的矩阵 \(B\)(\(A\) 的行与 \(B\) 的列相等),则 \(n\) 行 \(k\) 列的矩阵 \(C=A\times B\) 满足

\[c_{ij}=\sum_{l=1}^m a_{il}\times b_{lj} \]

只要枚举 \(i,j\)(范围是 \(n,k\)),并套用公式就能用 \(O(n^3)\) 的时间复杂度解决这个问题。

我知道,这看起来根本不是新手蒟蒻能看懂的。那我就用人话来讲讲矩阵乘法。

矩阵乘法并不是一个一个乘,而是行对应列乘。怎么个乘法呢?我们来看看下面两个矩阵相乘的例子。

\[\begin{pmatrix} 5 & 2 & 3 \\ 7 & 9 & 4 \\ \end{pmatrix} \times \begin{pmatrix} 2 & 6 & 8 & 1 \\ 0 & 9 & 1 & 3 \\ 2 & 4 & 4 & 1 \end{pmatrix} \]

第一个矩阵为 \(A\),第二个矩阵为 \(B\)。

我们先取出 \(A\) 的第一行。像这样:

\[\begin{pmatrix} 5 & 2 & 3 \end{pmatrix} \]

再取出 \(B\) 的第一列。像这样:

\[\begin{pmatrix} 2 \\ 0 \\ 2 \end{pmatrix} \]

不对,你给我转过来。

\[\begin{pmatrix} 2 & 0 & 2 \end{pmatrix} \]

现在终于可以相乘了。逐位相乘得出结果:

\[\begin{pmatrix} 5 & 2 & 3 \end{pmatrix} \times \begin{pmatrix} 2 & 0 & 2 \end{pmatrix} = \begin{pmatrix} 5\times2 & 2\times0 & 3\times2 \end{pmatrix} = \begin{pmatrix} 10 & 0 & 6 \end{pmatrix} \]

得出了结果 \(\begin{pmatrix} 10 & 0 & 6 \end{pmatrix}\)。再将每一位相加:

\[\begin{pmatrix} 10 & 0 & 6 \end{pmatrix} \to 10+0+6=16 \]

还记得我们之前是怎么取的吗?我们取了 \(A\) 的第一行和 \(B\) 的第一列(注意加粗部分),所以答案就存储在 \(C\) 的第一行第一列。还没搞懂?更通用一点:我们取了 \(A\) 的第 \(x\) 行和 \(B\) 的第 \(y\) 列(注意加粗部分),所以答案就存储在 \(C\) 的第 \(x\) 行第 \(y\) 列。也就是说,当我们想要获取矩阵 \(C\) 的第 \(x\) 行 \(y\) 列的时候,就需要取 \(A\) 的第 \(x\) 行和 \(B\) 的第 \(y\) 列,相乘再相加。由于 \(A\) 的行数与 \(B\) 的列数相等,取出来的数列才可以逐位相乘(不然元素个数不一样)。而取出来的数列长度就是 \(m\),所以可以用 \(O(m)\) 求和,总时间复杂度 \(O(nmk)=O(n^3)\)。

最后,可以看看代码辅助理解。

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

const int N = 105;

int n, m, k, a[N][N], b[N][N]; // 用二维数组存矩阵 A,B

int main() {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> a[i][j]; // 输入矩阵 A
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= k; j++)
            cin >> b[i][j]; // 输入矩阵 B
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= k; j++) { // 枚举 C 矩阵 n 行 k 列的每个元素
  	        // 以下部分为模拟刚刚讲的矩阵乘法
            int sum = 0; // 求和,sum 即为 C_ij
            for (int l = 1; l <= m; l++)
                sum += a[i][l] * b[l][j]; // 求和,A 的行和 B 的列,建议模拟一下过程加强理解
            cout << sum << " "; // 输出 sum(C_ij)
        }
        cout << "\n"; // 记得换行!
    }
    return 0; // 完美的结束
} 

这样就能愉快地切掉这道题了。请完成这道题再继续!

矩阵乘法满足以下性质:

结合律:\((AB)C=A(BC)\)

分配律:\((A+B)C=AC+BC\)

矩阵乘法不满足交换律。(这是重点!)

有了矩阵乘法,我们还可以结合上面的特殊矩阵得到一些性质:

\[A\times I=A \]

\[A\times 0_{m\times n}=0_{m\times n} \]

\[\huge\texttt{Part 4}\small\texttt{ Matrix fast power} \]

\[\color{#52C41A}\texttt{矩阵封装 & 矩阵快速幂} \]

快到今天的主题了!上例题

点开题目后的你 be like:

这是啥呀?

我来让题目描述“缩点水”:

给定一个 \(n\) 行 \(n\) 列的矩阵 \(A\),求 \(A^k\),即 \(\underbrace{A\times A\times A\times\cdots\times A\times A}_{k\texttt{ 次}}\)。

第一思路:暴力!直接做 \(k\) 次矩阵乘法,时间复杂度 \(O(kn^3)\)。看看数据范围:

\(0\le k\le10^{12}\)

考虑放弃做题。

那我们该怎么优化呢?看到需要计算 \(A^k\),我突然想到了一个算法:快速幂!但是矩阵快速幂该怎么写呢?答案是:和正常的快速幂一样,矩阵也能使用快速幂,只不过快速幂中的乘法变成了矩阵乘法。但是矩阵乘法太难写,有没有什么办法能让矩阵乘法也像普通的乘法一样,只要写一个 * 乘号就行了呢?

注意:不会快速幂的话可以先简单看看我写的文章

回到主题,有没有什么办法能只要写一个 * 乘号就能进行矩阵乘法呢?其实我们可以用结构体把矩阵封装起来,再用重载运算符就行了。关于重载运算符,可以参考这些资料

定义一个矩阵类型的结构体可以写成这样:

struct Matrix {
	
};

我们需要在里面用一个二维数组存储矩阵。我们还可以写一个结构体初始化函数,只要定义了一个矩阵,就自动清零,免去清零的麻烦。

struct Matrix {
	int a[N][N]; // N 为矩阵大小
	Matrix() {
		memset(a, 0, sizeof a);
	}
};

最后,把矩阵乘法写进去。

struct Matrix {
    ll a[N][N];
    Matrix() {
        memset(a, 0, sizeof a);
    }
    Matrix operator*(const Matrix &x)const {
        Matrix res;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                for (int k = 1; k <= n; k++)
                    res.a[i][j] = (res.a[i][j] % MOD + a[i][k] % MOD * x.a[k][j] % MOD) % MOD;
        return res;
    }
}; 

注意,这里一定要写成 a[i][k] * x.a[k][j],不能写成 x.a[i][k] * a[k][j],因为矩阵乘法不满足交换律!

这样,结构体封装部分就完成了。

我们要定义两个矩阵:\(a\) 和 \(base\)。\(a\) 是输入的矩阵,\(base\) 是答案矩阵,所以 \(base\) 需要初始化成 \(I\)(单位矩阵),写一个初始化函数 \(\operatorname{init}\),如下:

void init() {
    for (int i = 1; i <= n; i++) base.a[i][i] =1;
}

初始化完以后,就可以执行快速幂了,计算 \(A^k\) 了,让 \(base\) 乘 \(A\)。矩阵快速幂核心代码如下:

void expow(ll b) {
    while (b) {
        if (b & 1) base = base * a;
        a = a * a, b >>= 1;
    }  
}

有一点需要注意的就是,不能写成 base *= a 等形式,因为重载运算符定义的是 *,没有定义 *=,所以需要将 *= 展开。

最后,就可以输出 \(base\) 了。展示全部代码:

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

typedef long long ll;

const int N = 105, MOD = 1e9 + 7;

int n;
ll k;

struct Matrix {
    ll a[N][N];
    Matrix() {
        memset(a, 0, sizeof a);
    }
    Matrix operator*(const Matrix &x)const {
        Matrix res;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                for (int k = 1; k <= n; k++)
                    res.a[i][j] = (res.a[i][j] % MOD + a[i][k] % MOD * x.a[k][j] % MOD) % MOD;
        return res;
    }
}a, base; 

void init() {
    for (int i = 1; i <= n; i++) base.a[i][i] =1;
}

void expow(ll b) {
    while (b) {
        if (b & 1) base = base * a;
        a = a * a, b >>= 1;
    }  
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> a.a[i][j];
    init();
    expow(k);
    for (int i = 1; i <= n; putchar('\n'), i++)
        for (int j = 1; j <= n; j++)
            cout << base.a[i][j] << " ";
    return 0;
} 

\[\huge\texttt{Part 5}\small\texttt{ Matrix acceleration} \]

\[\color{#52C41A}\texttt{矩阵加速} \]

终于到了最后的 \(\color{red}\texttt{BOSS 关卡}\) 了!你们有信心吗?加油!

点击此处进入 \(\color{red}\texttt{BOSS 关卡}\) ......

点开题目 \(\color{red}\texttt{BOSS 关卡}\) 后的你 be like(梅开二度):

这和矩阵有什么关系吗???

我直接一个递推!

  • 对于 \(100\%\) 的数据 \(1 \leq T \leq 100\),\(1 \leq n \leq 2 \times 10^9\)。

\(O(Tn)\) 这 \(2\times10^{11}\) 的复杂度实在无法接受。

(呜呜呜我再也不学 c艹 了)

没关系,先看看思路!

因为发现当 \(x\le3\) 时答案为 \(1\),所以这是最基础的情况。我们可以构造一个只有一列的矩阵:

\[\begin{pmatrix} a_3 & a_2 & a_1 \end{pmatrix} = \begin{pmatrix} 1 & 1 & 1 \end{pmatrix} \]

显然,这三个元素都是 \(1\)。

那么,假设我想要得到 \(a_4\),该怎么办呢?所以,我们需要进行一种运算,让上面的矩阵变化一下,像这样:

\[\begin{pmatrix} a_3 & a_2 & a_1 \end{pmatrix} \to \begin{pmatrix} a_4 & a_3 & a_2 \end{pmatrix} \]

更加通用一点:

\[\begin{pmatrix} a_x & a_{x-1} & a_{x-2} \end{pmatrix} \to \begin{pmatrix} a_{x+1} & a_{x} & a_{x-1} \end{pmatrix} \]

可以发现,矩阵中的每个元素的项数都向前推进了 \(1\)。那么,我们大概可以写出伪代码:


如果 \(x\le3\)

输出 \(1\)

否则

执行运算 \(n-3\) 次(重要!)

并输出答案矩阵 \(1\) 行 \(1\) 列


特判(对于特殊情况的判断)和输出应该没什么问题,主要是为什么运算恰好要执行 \(n-3\) 次呢?稍微画个图模拟一下就好了。

还是假设要获取 \(a_4\),则执行运算 \(4-3=1\) 次。在执行 \(1\) 次运算后,

\[\begin{pmatrix} a_3 & a_2 & a_1 \end{pmatrix} \]

变为

\[\begin{pmatrix} a_4 & a_3 & a_2 \end{pmatrix} \]

这样就刚好在第 \(1\) 行 \(1\) 列得到 \(a_4\) 啦!

那么,说了这么久,这个神秘的运算是什么呢?当当当当~,他就是我们的——矩阵乘法!

没错,所谓的变换,其实就是乘上了一个特殊的矩阵!那么,这个矩阵长什么样呢?让我们一起来推理吧。

(此处应配上推理の小曲)

我们可以先列一个表格,表格的行代表矩阵 \(\begin{pmatrix}a_3 & a_2 & a_1 \end{pmatrix}\) 的元素,列代表递推时与这些元素相关的元素。像这样:(表格可能在博客里渲染不出来,凑合着看吧,抱歉)

\(a_x\) \(a_{x-1}\) \(a_{x-2}\)
\(a_{x-1}\)
\(a_{x-2}\)
\(a_{x-3}\)

好了,对于 \(a_x\),我们该怎么填他那一列呢?我们可以观察到递推式 \(a_x=a_{x-1}+a_{x-3}\),所以有:

\[a_x=a_{x-1}\times1+a_{x-2}\times0+a_{x-3}\times1 \]

观察系数 \(1,0,1\),把这些系数填入表格中:

\(a_x\) \(a_{x-1}\) \(a_{x-2}\)
\(a_{x-1}\) \(1\)
\(a_{x-2}\) \(0\)
\(a_{x-3}\) \(1\)

后面的也以此类推:

\[a_{x-1}=a_{x-1}\times1+a_{x-2}\times0+a_{x-3}\times0 \]

\[a_{x-2}=a_{x-1}\times0+a_{x-2}\times1+a_{x-3}\times0 \]

\(a_x\) \(a_{x-1}\) \(a_{x-2}\)
\(a_{x-1}\) \(1\) \(1\) \(0\)
\(a_{x-2}\) \(0\) \(0\) \(1\)
\(a_{x-3}\) \(1\) \(0\) \(0\)

这样,我们就可以推出这个神秘的矩阵了:

\[\begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} \]

好了,现在我们终于知道了,一次神秘操作,就是将让 \(\begin{pmatrix}a_3 & a_2 & a_1 \end{pmatrix}\) 这个矩阵乘上\( \begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} \)。这时候就有人问了:

一次矩阵乘法的时间复杂度还没有递推快,这根本就没有优化嘛。

等等!我们把这个式子展开:

\[\begin{aligned} {} & \begin{pmatrix} 1 & 1 & 1 \end{pmatrix} \times \begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} \times \begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} \cdots \times \begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} \\ = & \begin{pmatrix} 1 & 1 & 1 \end{pmatrix} \times \begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} ^{n-3} \end{aligned} \]

不是吧!这居然变成了一个矩阵快速幂?!!

也就是说,我们可以用快速幂计算 \(\begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} ^{n-3}\),并乘上初始矩阵 \(\begin{pmatrix} 1 & 1 & 1 \end{pmatrix}\)。这样,我们成功地把时间复杂度从 \(O(Tn)\) 优化到了 \(O(T\log n)\)!(矩阵快速幂是 \(O(\log n)\),因为矩阵很小,矩阵乘法只计算 \(9\) 次,是一个很小的常数)

下面奉上代码:(标准的矩阵加速思想)

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

typedef long long ll;

const int MOD = 1e9 + 7;

int T, n;

struct Matrix {
    ll a[5][5];
    Matrix() {
        memset(a, 0, sizeof a);
    }
    Matrix operator*(const Matrix &x)const { // 矩阵乘法
        Matrix res;
        for (int i = 1; i <= 3; i++)
            for (int j = 1; j <= 3; j++)
            	for (int k = 1; k <= 3; k++)
                    res.a[i][j] = (res.a[i][j] % MOD + a[i][k] % MOD * x.a[k][j] % MOD) % MOD;
        return res;
    }
    void mems() {
    	memset(a, 0, sizeof a);
	}
}ans, base; 

void init() { // 初始化两个矩阵
	ans.mems(), base.mems(); // 记得清空!
	ans.a[1][1] = ans.a[1][2] = ans.a[1][3] = 1;
	base.a[1][1] = base.a[1][2] = base.a[2][3] = base.a[3][1] = 1;
}

void expow(int b) { // 矩阵快速幂,是在 ans 矩阵的基础上乘的
    while (b) {
        if (b & 1) ans = ans * base;
        base = base * base, b >>= 1;
    }  
}

int main() {    
    cin >> T;
    while (T --) {
        cin >> n;
        init(); // 初始化不能忘
        if (n <= 3) { // 特判
            cout << "1\n";
            continue;
        } 
        expow(n - 3); // 计算特殊矩阵的 n - 3 次方,已经乘到了 ans 里
        cout << ans.a[1][1] << "\n"; // 输出答案!芜湖!
    }
    return 0; // 快乐结束
} 

就这样,我们完成了矩阵加速递推。

再次声明矩阵快速幂(矩阵加速)时间复杂度:\(O(N^3\log n)\),其中 \(N\) 为矩阵的行数(列数),\(n\) 为快速幂的规模 \(a^n\)。

小提示:关于 \(base\) 矩阵的构造

就是这个 \(\begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix}\) 矩阵。

可以这样:我们要推导出 \(\begin{pmatrix}a_x & a_{x-1} & a_{x-2}\end{pmatrix}\),那么这个矩阵从哪里来?当然是从 \(\begin{pmatrix}a_{x-1} & a_{x-2} & a_{x-3}\end{pmatrix}\) 来。所以,表格才长这样:

\(a_x\) \(a_{x-1}\) \(a_{x-2}\)
\(a_{x-1}\)
\(a_{x-2}\)
\(a_{x-3}\)

那么,能不能构造一个行列数各不相同的矩阵,而不是一个 \(n\times n\) 的矩阵呢?答案是不可以,因为我们要计算 \(\begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix}\) 这种矩阵的幂,那如果行和列不相等,相乘的两个矩阵的行列也不相等,就无法进行矩阵乘法。比如这个:

\[\begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} \times \begin{pmatrix} 1 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix} \]

可以看到,左边 \(2\) 行,右边 \(3\) 列,显然不相等,无法进行矩阵乘法。

\[\huge\texttt{Part 6}\small\texttt{ Thank you!} \]

\[\color{#FE4C6E}\texttt{你}\color{#F39C11}\texttt{居}\color{#FFC116}\texttt{然}\color{#52C41A}\texttt{看}\color{#3498DB}\texttt{完}\color{#9D3DCF}\texttt{了}\color{#0E1D69}\texttt{!} \]

这篇文章花费了我很多时间,希望你喜欢!

对了,你学会了吗?是不是,矩阵也并没有那么难?

这应该是我的【精选】文章中的第一篇,没想到写的是矩阵方面的。

总之,很感谢你的阅读!希望你能从我这学到点东西!

再见!

标签:begin,end,矩阵,精选,cdots,pmatrix,加速,vdots
From: https://www.cnblogs.com/Weekoder/p/18237764

相关文章

  • 60款柯达Kodak电影胶片调色预设SP-3000扫描仪配置文件2383电影卷2393视频lut优质精选
    60款柯达Kodak电影胶片调色预设SP-3000扫描仪配置文件2383电影卷2393视频lut优质精选预设预设在精不在多,素材湾倾心提供优质精选预设并整理安装使用教学助你学习工作中提升效率,更多时间专心于优质创作。*包含内容:5组共60款柯达电影胶片调色预设1款柯达SP-3000扫描仪配置(XMP......
  • uniapp零基础到项目实战( 附送250套精选项目源码),全网最全保姆式教程
    今天开始带着大家一起零基础学习uniapp,在下面的课程中我们就简称uniapp为uni吧。我这里从零基础开始教大家,后面可以带大家简单的做一个实战项目。所以不用担心自己没有基础,跟着认真学习就行了的。一,认识uniapp1-1,uniapp的好处我们学习uniapp之前先要认识uniapp的好处看下......
  • 袋鼠云产品功能更新报告10期|智能进化,近百项功能升级加速数智化转型
    欢迎查阅袋鼠云第10期产品功能更新报告。本期,我们精心推出了72项新增和优化功能,致力于在数字化浪潮中为您提供更高效、更智能的服务。我们相信,这些新特性将为您的业务注入新活力,确保您在数字化转型的每一步都坚实而有力。以下为袋鼠云产品功能更新报告第10期内容,更多探索,请继续阅......
  • 螺旋转动,矩阵的舞蹈:JavaScript中实现螺旋矩阵遍历算法
    螺旋转动,矩阵的舞蹈:JavaScript中实现螺旋矩阵遍历算法基础概念:什么是螺旋矩阵?核心算法解析示例一:基础螺旋矩阵遍历算法解析进阶技巧示例二:动态生成螺旋矩阵技巧点实战与性能优化问题与解决:大矩阵处理结语与讨论在编程的奇幻世界里,数组与矩阵是构筑数字城堡的基石......
  • 【JS封装-数组操作】强化编程实践:精选JavaScript函数封装集锦-关于数组操作(数组去重、
    目录数组去重数组快速排序过滤数组映射数组数组扁平化数组求和数组最大值数组最小值数组切片数组乱序(洗牌算法)数组去重/***去除数组中的重复项。*@param{Array}array要去重的数组。*@returns{Array}去重后的数组。*/functionuniqueArray(array......
  • 197、加速Django应用:性能优化全攻略
    Python开发框架Django之性能优化:提高应用程序的运行速度本文将为大家介绍如何使用Python开发框架Django进行性能优化,以提高应用程序的运行速度。我们将探讨一系列的技巧和最佳实践,帮助你优化Django应用程序的性能,从而提供更好的用户体验。1.概述在当今的数字化时代,应用......
  • 【GIS教程】土地利用转移矩阵
             随着科技社会的不断进步,人类活动对地理环境的影响与塑造日益明显,土地不断的侵蚀与改变也导致一系列的环境问题日益突出。土地利用/覆盖(LUCC)作为全球环境变化研究的重点问题为越来越多的国际研究机构所重视,研究它的变化既是对已有的工业化、城市化过程的一个......
  • Simd库——图像处理领域的CPU指令集加速库
    Simd库是一个免费的开源图像处理和机器学习库,专为C和C++程序员设计。它为图像处理提供了许多有用的高性能算法,例如:像素格式转换,图像缩放和过滤,从图像中提取统计信息,运动检测,对象检测(HAAR和LBP分类器级联)和分类,神经网络。官网 SimdLibrary(ermig1979.github.io),可以下载编译,函......
  • CUDA与架构矩阵概览
    CUDAToolkit、驱动程序与架构矩阵CUDA驱动程序提供了向后兼容的API,因此新版本的NVIDIA驱动程序总是能与(使用旧版本CUDAToolkit编译的)应用程序协同工作。CUDA的这一行为在此处有详细记录。然而,每个CUDAToolkit都需要最低版本的NVIDIA驱动程序作为支持。相应地,当使用如nvidia-sm......
  • 代码随想录第2天 | 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,数组总结
    题目:977.有序数组的平方思路:first.for循环,平方,然后sort排序,提交通过啊哈~|但时间复杂度大O(n+nlogn),->O(nlogn)的时间复杂度,题目进阶要求时间复杂度为O(n)second.双指针。题目为有序数组,包含负数,则平方后,最大值在数组的两头,则使用双指针进行,两个比较大小,大的存入新......