矩阵浅谈qwq

矩阵:

前置知识:

矩阵,定义太长了,其实就是个屑二维数组。(

他的运算也比较毒瘤。

  • 加减法:

太屑了。

比如两个矩阵相加,其实就是对应位置加减去对应位置的数就行了。

  • 乘除法:

乘法很毒瘤,但这就是定义,只有一个 \(a \times b\) 和一个 \(b \times c\) 的矩阵才能相乘,这里前后的顺序不能颠倒,因为这就是毒瘤定义,当且仅当一个矩阵的长等于另一个矩阵的宽才能乘。

乘完之后就乘了一个 \(a \times c\) 的矩阵。

然后我们现在只知道这样能乘,和乘完之后成为一个什么样的矩阵,那里有的值是啥呢。

我们设一个矩阵 \(A\) 为一个 \(a \times b\) 的矩阵,然后我们再设一个矩阵 \(B\) 为一个 \(b \times c\) 的矩阵,我们由前面告诉你的毒瘤定义可知, \(A \times B\) 后的矩阵 \(C\) 为一个 \(a \times c\) 的矩阵。

我们有以下公式可以求出矩阵中任意一个点的元素大小:

\[C_{i,j} = \sum_{r=1}^{b} A_{i,r} \times B_{r, j} \]

其实感觉有点像向量,虽然好像一点关系也没有。

  • 数乘运算:

就是一个数乘一个 \(n \times m\) 矩阵,这个时候你可能就要问了啊,那你把一个数看成一个 \(1 \times 1\) 的矩阵,那这不就是不满足你的定义了吗。

这个时候我们跳出规则,把格局打开,再重新定义一手。

矩阵的数乘就是把这个数乘到每一个 \((i, j)\) 元上

  • 矩阵转置:

就是把一个矩阵旋转 \(90^{o}\)

这就是矩阵的一些基础前置知识,然后我们来讲矩阵快速幂。

  • 单位矩阵

就相当于一个 \(1\), 主对角线上全为 \(1\),其余地方为 \(0\) 的矩阵就是单位矩阵。

矩阵快速幂

这可是个好东西。

比如你要得出了一个递推式,奈何他让你递推 \(10^{18}\),那你怎么办,那就用矩阵加速递推,那怎么加速递推呢,就用矩阵快速幂。

我们根据前面的定义知道一个 \(n \times m\) 矩阵 \(A\) 是可以不断的乘自己的,就是不断的转置,然后乘。

我们想要求出 \(A^k\)

那该怎么求呢。

我们仿照快速幂的方法来求。

我们用一个结构体来存:

struct node {
	int a[MAXN][MAXN];
    node() {memset(a, false, sizeof a);}
	void build() {for(int i = 1; i <= n; i++) a[i][i] = 1;} //构造单位矩阵
    void print() {
        for(int i = 1; i <=n;cout << '\n', i++){
			for(int j = 1; j <= n; j++) {
                cout << a[i][j] << " ";
            }
        }
    } 
}

应该没有人看不懂吧。


然后乘法就是仿照之前提到的式子模拟即可。

node operator * (const node &x, const node &y) {
	node z;
    for(int l = 1; l <= n; l++) {
		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][l] * y.a[l][j] % mod) % mod;
            }
        }
    }
    return z;
}

\(z\) 就是我们 \(x \times y\) 的矩阵。


然后具体求解的时候也就是 \(A^k\)

这里 \(ans\)\(A^k\)\(b\) 就是 \(A\)

node ksm(node A, int k) {
	node res;
	res.build();
	while(k) {
		if(k & 1) res = res * A;
		A = A * A, k >>= 1;	
	}
	return res;
}

然后我们就愉快的将模板题切掉了。

矩阵加速递推:

那什么样的递推式才能用矩阵加速递推呢,就是给你前几项,然后后面的等于前面某几项的和,要是按照递推式推过去会超时,这个时候就需要用到矩阵来加速递推了。

具体是怎么递推呢,我们需要构造两个矩阵,一个是初始矩阵,一个是递推矩阵,这个递推矩阵一般是一个 \(3 \times 3\) 的矩阵。

比如我们的通项公式为 \(a_i = a_{i - 1} + a_{i - 3}\),然后我们知道 \(a_1, a_2, a_3\),如果我让你求 \(a_{1e8}\)

我们就用初始矩阵去乘上递推矩阵的 \(10^8\) 就能得到了。

那怎么去构造这两个矩阵呢。

画个图来感性理解一下 qwq

\(\begin{vmatrix}a_{i - 1}\\a_{i - 2}\\a_{i - 3}\end{vmatrix}\) \(\times\) \(\begin{vmatrix}z&&h&&b\\x&&j&&p\\s &&s&&h\end{vmatrix}\) \(=\) \(\begin{vmatrix}a_{i}\\a_{i - 1}\\a_{i - 2}\end{vmatrix}\)

我们要求出第二个矩阵,观察一下这两个矩阵 \(a_{i - 1}\)\(a_i\) 相对,然后根据通项公式可知,\(a_{i} = a_{i - 1}\ +\ a_{i - 3}\),所以 \(z\ =\ 1\ \&\ x\ =\ 0\ \&\ s\ =\ 1\)。剩下的一个样。

最后我们求出来第二个矩阵 :

\(\begin{vmatrix}1&&1&&0\\0&&0&&1\\1 &&0&&0\end{vmatrix}\)

然后开始乘就行了,其实挺简单的。

代码贴贴:

struct node {
	int a[MAX][MAX];
	node() {memset(a, false, sizeof a); }
	void build() { for(int i = 1; i <= 3; i++) a[i][i] = 1; }  
}Frist, t;

node operator * (const node &x, const node &y) {
	node res;
	for(int i = 1; i <= 3; i++) {
		for(int k = 1; k <= 3; k++) {
			for(int j = 1; j <= 3; j++) {
				res.a[i][j] = (res.a[i][j] + x.a[i][k] * y.a[k][j] % mod) %mod;
			}
		}
	}return res;
}

node Ksm(node x, int k) {
	node res;
	res.build();
	while(k) {
		if(k & 1) res = res * x;
		x = x * x, k >>= 1;
	}return res;
}
Frist.a[1][1] = Frist.a[1][2] = Frist.a[1][3] = 1;
t.a[1][1] = t.a[1][2] = t.a[2][3] = t.a[3][1] = 1;
posted @ 2022-05-20 20:01  TLE_Automation  阅读(51)  评论(0编辑  收藏  举报