线性代数学习笔记(一):线性代数基础知识

向量

定义

从偏计算机的角度分析,这是排成一列的数。

从偏物理的角度分析,这是一条有方向有长度的线段。

可以通过数形结合的方式来理解向量。

向量的表示法

虽然向量的起点不固定,但画平面直角坐标系中的向量,我们一般将向量的起点放在 (0,0),用向量的终点表示这个向量,如图:

这个向量可以表示成 (a,b),用计算机的表现方式,也可以写作 [ab]

模长:向量 [ab] 的长度;

零向量:模长为 0 的向量;

单位向量:模长为 1 的向量。

运算

详见多项式学习笔记(一)(2024.7.6)

其实,任何一个向量 v 的坐标表示 (a,b),都可以表示成 v=ai^+bj^,其中 i^j^ 分别是 x 轴和 y 轴方向上的单位向量。

观察到,平面中的所有向量都可以表示成 i^j^ 的线性组合,此时 i^j^ 就称作这个平面的一组基。注意到,只要是非共线的两个向量,都可以作为这个平面的一组基。而共线的两个向量,只能作为它们所在直线的一组基。

扩展而言,非共面的三个向量 i^j^k^ 可以作为空间的一组基,共面不共线的 3 个向量可以作为平面的一组基,更高维空间同理。

张成空间

如果 i^j^ 是这个平面的一组基,那么这个平面就叫做这两个向量的张成空间,更高维空间同理。

矩阵

Unfortunately, no one can be told what the Matrix is.

You have to see it for yourself.

—— Morpheus, The Matrix

定义

n×m 个数 ai,j 排成的 nm 列的数表称为 nm 列的矩阵 A,简称 n×m 矩阵 A。记作:

A=[a1,1a1,2a1,ma2,1a2,2a2,man,1an,2an,m]

单位矩阵(对角矩阵):一个 n×n 的矩阵 I,除了对角线上的数为 1,其他位置全为 0 那么这个矩阵就被叫做单位矩阵:

I=[100010001]

运算

  • 加法:一个 n×m 的矩阵 A,加上一个 n×m 的矩阵 B,得到一个 n×m 的矩阵 C,其中 ci,j=ai,j+bi,j

  • 乘法:一个 n×p 的矩阵 A,乘以一个 p×m 的矩阵 B,得到一个 n×m 的矩阵 C,其中 ci,j=k=1pai,kbk,j

其实矩阵乘法是一个很反直觉的事情,应为按理说矩阵乘法也应该像矩阵加法一样,对应位置相乘。但现实中的矩阵乘法确实 A 的每一排与 B 的每一列对应位置相乘再相加,这其实得从矩阵的来源讲起。

其实矩阵一开始是用来简化表示多元一次方程组的,比如以下二元一次方程方程组:

{a1,1x+a1,2y=c1a2,1x+a2,2y=c2

将它改写成矩阵形式:

[a1,1a1,2a2,1a2,2][xy]=[c1c2]

此时是一个矩阵乘以一个向量的形式,可以初步感受到矩阵乘法的法则是对的。

下面考虑将 a1,1x+a1,2y 看做一个整体,a2,1x+a2,2y 看做一个整体,再继续往上累积系数:

{b1,1(a1,1x+a1,2y)+b1,2(a2,1x+a2,2y)=d1b2,1(a1,1x+a1,2y)+b2,2(a1,2x+a2,2y)=d2

重新整理 x,y 可得:

{(b1,1a1,1+b1,2a2,1)x+(b1,1a1,2+b1,2a2,2)y=d1(b2,1a1,1+b2,1a1,2)x+(b2,2a1,2+b2,2a2,2)y=d2

将原式改写成矩阵形式:

[a1,1a1,2a2,1a2,2][b1,1b1,2b2,1b2,2][xy]=[d1d2]

可以发现,如果运用矩阵乘法,乘出来的矩阵再变为二元一次方程组的形式,会和整理后的方程一模一样,这也是为什么矩阵乘法要这样定义了。

  • 转置:一个 n×m 的矩阵 A,转置成一个 m×n 的矩阵 B,其中 Bi,j=Aj,i

对于一个矩阵 A,如果存在一个矩阵 B,使得 BA=I,那么称 BA 的逆矩阵,具体求法在高斯消元时再来整理。

线性变换

线性变换是一个由一个向量映射到另一个向量的函数,满足:

  • 原本终点同一条直线上的向量变换后终点仍在同一条直线上;

  • 原来的零向量变换后还是零向量。

注意到,在线性变换中,一个向量的不同部分的缩放比是相同的,否则会将之前的直线变为曲线,比如离原点越远,放大比例就越大的变换:

这种变换会形成曲线,因此不是线性变换。

而剪切这种变换,将横着的线段不变化,竖着的线段顺时针倾斜 45

这种变换符合线性变换的性质,因此剪切变换是线性变换。

由于一个向量不同部分的缩放比相同,因此如果 v=ai^+bj^,那么一定可以找到这个平面新的一组基 i^j^,使得 v 经过该线性变换后得到的 v=ai^+bj^(在剪切变换中,i^=[10]j^=[11]),那么如果我们将新得到的两个基写在一起,成为一个 2×2 的矩阵(剪切变换中为 [1101]),那么我们就可以用矩阵表示一个线性变换,这也可以说明矩阵乘法具有结合律。

行列式

我们讲到,在线性变换中,向量的模长与方向都发生了变化,那么平面上的所有正方形的面积都发生了变化,如果把正方形的大小变得无限接近于 0,那么平面上任何一个图形都可以看做由这些小正方形组成,因此它们面积的变化都一样,而行列式正是用来刻画面积变换的一个函数。

记一次线性变换的面积变化为 |det(A)|,其中 det(A) 的正负由平面是否翻转决定(可以感性理解一下),它的计算式为 det(A)=π(1)r(π)i=1nMi,πi,其中 π 表示任意一个 1n 的排列,而 r(π) 则是 π 的逆序对数量。

其实对于二维空间,n 的排列只有 {1,2}{2,1} 两种,那么行列式的计算可以简化成 det([abcd])=adbc,可以根据大佬zwfymqz的博客中的图片:

来理解。接着你会发现中小学的新定义运算中的一类经典题其实就是在求二维矩阵的行列式,原来我小学就会线性代数了啊

det(M)=0 时,也就是说这个变换使得小正方形的面积变为了 0(比如映射到了同一条直线上),此时这个变换一定使空间的某一维发生了坍缩。反过来,若一个变换使得空间的某一维发生了坍缩,那么这个变换的行列式一定为 0

行列式有一个重要的性质:det(M1)det(M2)=det(M1M2),可以理解为先将“小正方形的面积”扩大 k1,再将“小正方形的面积”扩大 k2,相当于将“小正方形的面积”扩大了 k1k2 倍,这在求初等变换的某些性质时很有用。

列空间

我们知道一个线性变换可以通过将变换后的基拼在一起成为一个矩阵来表示,而这一组基张成的空间就成为这个矩阵的列空间。

这与之前介绍的矩阵与线性方程组的关系有所联系,如果等式右边的答案向量不在该系数矩阵的列空间中,那么这个线性方程组就是无解的,因为无论如何都找不到一个向量,在进行该线性变换后,可以得到答案向量。

一个线性变换的列空间的维度,如果一个线性变换前的秩与变换后的秩没发生变化,那么该线性变换就未造成空间的坍缩,否则该线性变换就使空间的某些维坍缩了。

如果一个变换的秩是该变换能达到的最大的秩,那么就称该线性变换的矩阵是满秩的,比如一个 2×2 的矩阵,如果它的秩是 2,那么他就是满秩的。

零空间

如果一个线性变换使空间的某一维坍缩了,那么会有一些向量被映射到了原点,这些向量组成的空间就是零空间。零空间可以用来表示一个线性方程组解的个数。

点积

定义两个向量 u=[x1x2xn]v=[y1y2yn] 的点积为 uv=x1y1+x2y2++xnyn,乍一看长相平平,但其实向量的点积与线性变换有紧密的联系,这也是为什么放在线性变换来讲,而不是向量的原因。

其实,点积有它的几何性质,如图:

两个向量的点积,等于一个向量 w 在另一个向量 v 上的投影的长度与 v 的模长的乘积。

很显然,如果 wv 上的投影与 v 的方向相同,那么点积为正,否则点积为负。

下面我们来证明为什么是这样的。

考虑到两个向量的乘积变成了一个数字,其实是发生了空间的坍缩,而这恰好可以用矩阵来表示,那么现在就需要找到一个从二维空间到一维空间的线性变换。

首先,由于该线性变换的秩是 1,也就是说列空间的维度是 1,那必然 i^j^ 也只是一个数,可以用 [ab] 来表示。

考虑到一维空间就是一条数轴,那么我们可以将数轴放上平面直角坐标系,记这条数轴的的基为 u^,为了方便,假设它的模长为 1

那么此时,平面上的任何一个点,往这条数轴作垂线,都可以得到一个数:

这时,我们其实已经发现这个变换了,现在考虑的就是如何将 u^i^j^ 来表示,这样就可以得到线性变换的矩阵了。我们发现其实可以构造出一对对称性的全等三角形:

现在来考虑变换后的 i^ 和变换后的 j^ 分别是什么。根据这两组全等三角形,我们可以发现 i^ 变成了 u^ 的横坐标,j^ 变成了 u^ 的纵坐标,那么这个线性变换就能表示成 [u^xu^y],现在你会惊奇的发现,这个矩阵乘上一个向量,就等于 u^ 与这个向量进行点乘。那么这时一个向量 v^u^ 点乘,也就相当于将 u^ 投影到数轴上,也就是 u^ 所在的直线上。

现在考虑模长不是 1 的向量 s,那么 s 可以表示成 au^as 的模长,那么此时的线性变换矩阵就可以写成 [au^xau^y]=a[u^xu^y]。此时,如果一个向量 t 要与 s 点乘,那么相当于要先将 t 投影到 s 所在的直线,再将投影的长度与 s 的模长相乘,此时点积的几何意义证明成立。

其实,点积是存在交换律的,可以计算证明,也可以通过射影定理证明,此处不再赘述。

叉积

定义向量 uv 的叉积 u×v 为以 uv 作为临边的平行四边形的面积,叉积的值可以通过行列式求出。

可以发现,由于行列式可正可负,因此叉积的值也可正可负,这取决于平面是否翻转。如果 uv 进行叉积,则 u×v 为正,v×u 为负。

事实上,狭义的叉积只对三维空间有用。当两个三维空间中的向量 uv 做叉积时,可以得到另一个向量 w,满足这个向量的模长为以 uv 作为临边的平行四边形的面积,垂直于这个平行四边形,且满足右手定则。如果用计算式来表示,就是 [u1u2u3]×[v1v2v3]=det([i^u1v1j^u2v2k^u3v3])=(u2v3u3v2)i^+(u3v1u1v3)j^+(u1v2u2v1)k^,接下来证明为什么可以这样计算。

我们先假设不知道如何计算两个三维向量的叉积,此时,通过二维空间两个向量的叉积合理外推,三维空间的叉积应该需要三个向量 u,v,w,它们的叉积 u×v×w 表示以 u,v,w 作为临边的平行六面体的体积,此时可以用 det([u1v1w1u2v2w2u3v3w3]) 来表示。

这时我们把 u 这个向量当做主元,将叉积看做一个函数,此时可以得到 f([u1u2u3])=det([u1v1w1u2v2w2u3v3w3]),可以发现这是一个从三维向量到一位向量的线性变换,因此仿照点积,这个函数可以写作 [xyz][u1u2u3]=det([u1v1w1u2v2w2u3v3w3]),又可以写成 [xyz][u1u2u3]=det([u1v1w1u2v2w2u3v3w3])。现在我们就需要找到这个向量 p

从计算的角度思考,等式左边为 xu1+yu2+zu3,等式右边为 (v2w3v3w2)u1+(v3w1v1w3)u2+(v1w2v2w1)z,此时我们已经可以求出这个向量,现在我们发现这个向量其实与 u 没有任何关系,于是如果 u=[i^j^k^],对 p 的取值也无影响。因此这里的 i^,j^,k^ 只是为了方便计算,并无实际意义。

接着来求解叉积的几何性质。

首先,我们知道点积是将一个向量投影到另一个向量上,再乘以这个向量的模长。

现在,我们知道,pu 的点积,等于以 uvw 为临边的平行六面体的体积,由于平行六面体的体积等于底面面积乘以高,于是不妨将 u 投影到垂直于 vw 的直线上:

那么,这个平行六面体的体积就等于底面积与投影的积,而这正好等于 u 与垂直于 vw 且模长等于底面积的向量的点积,因此 p 的几何性质得以证明。

特征向量与特征值

定义一个线性变换 M,如果存在一个向量 v 和一个常数 a 使得 Mv=av,那么就称 vM 这个线性变换的特征向量,a 为这个变换的特征值。

可以发现 v 这个向量在 M 变换中只发生了模长的变化,因此可以比较准确的刻画 v 的变化,也方便计算矩阵的幂。

以斐波那契数列为例。首先,斐波那契数列的递推公式为 f1=f2=1,fi=fi1+fi2(i>1),可以写成 fi=1×fi1+1×fi2,而 fi1 又可以写成 fi1=1×fi1+0×fi2,发现这和矩阵乘法很类似,于是将其改写成矩阵形式为 [fi1fi2][1110]=[fifi1]

假设特征向量 v=[xy],那么 Mv=av,发现这个式子硬解有 3 个未知数,但只有两个方程,考虑把 av 变成一个矩阵乘以一个向量的形式,也就是 [a00a][xy],将此式移到等号左边,再进行矩阵减法,可以得到 [1a11a][xy]=0

显然,当 x=y=0 时等式成立,但这很没有意思,靠虑当 xy 中最多有 10 时,可以发现该变换将一条直线映射到了一个点,发生了维度的坍缩,依据行列式的结论,此时 det([1a11a]) 一定为 0,即 a(1a)1=0,整理得 a2a1=0,解得 a1=1+52,a2=152,证明每进行一次 M 变换,有一个特征向量的长度变为了原先的 1+52,另一个变为了原先的 152

接下来求解特征向量,将 a=1+52 带入原先的等式 Mv=av,得到 {x+y=1+52xx=1+52y,但这会解出无穷多组解,因为在某个特征向量所在直线上的所有向量在 M 变换中都只发生模长的变化,就以 [1+52] 为例。

同理,可以求出另一个特征向量 [152]

基变换

有些时候,我们用的基是 i^j^,并通过这一组基的线性组合来表示其他向量,但题目中的一个向量可能是通过另一组基 u^v^ 的线性组合来表示的,这时我们就需要通过基变换来将此向量用 i^j^ 来表示。

假设 u^v^i^j^ 的张成空间中表示为 pi^+qj^si^+tj^xu^v^ 的张成空间中表示为 au^+bv^,那么其实 x=(ap+bs)i^+(aq+bt)j^,更近一步说就是 [ab] 左乘上了 [psqt],这时我们就可以把用别的基表示的向量变换到需要用的基中来。

当我们需要把别的基中的向量运用当前基的线性变换时,只需要乘上 [psqt] 将该向量转到当前基中,进行线性变换后再乘上 [psqt]1 把变换后的向量再转到别的基就可以了。形式化的,如果要把以 u^v^ 为基的向量在当前基中进行剪切变换,那么需要求的就是 [psqt]1[1101][psqt][ab]

这就是基变换的基础知识,它最直接的应用就是求递推函数的通项公式,下面以斐波那契数列为例。

求递推函数通项公式

我们知道斐波那契数列的转移矩阵是 [1110],已经可以用矩阵快速幂来做了,但发现这个式子很像一个线性变换的式子。其实可以发现,把初始的矩阵 a=[f2f1]=[11] 进行 n2M=[1110] 变换,答案就为向量的第一个数字,但你会发现,进行一次该变换后,该向量的方向和模长都发生了变化,使得很难准确刻画该变换,这时我们就需要特征值与特征向量的帮忙。

我们已经知道了斐波那契数列的转移矩阵的特征向量是 [1+52][152],考虑以 [1+52][152] 作为基,那么基变换矩阵 A 就是 [1+51522],由于 M 这个变换在 A 中这一组基就只有模长的变化,而模长的变化正好是特征值,因此 M 就可以写成 [1+51522][5+1200152][1+51522]1

变换 1 次是 Ma,那么可以改写成 [1+51522][5+1200152][1+51522]1a

变换 n2 次,就可以得到 ([1+51522][5+1200152][1+51522]1)n2a

将逆矩阵求出来,原式就变成了 ([1+51522][5+1200152][51055205105+520])n2a

由于 AMA1×AMA1=AM2A1,那么原式就会变成 [1+51522][(5+12)n200(152)n2][51055205105+520]a

接着进行普通的矩阵乘法,那么原式

=[1+51522][(5+12)n200(152)n2][5+5205520]=[1+51522][(5+12)n25+520(152)n25520]=[1+51522]510[(5+12)n1(152)n1]=510[(1+5)(5+12)n1(15)(152)n12(5+12)n12(152)n1]=55[(5+12)n(152)n(5+12)n1(152)n1]=[fnfn1]

因此,fn=55[(5+12)n(152)n]

高斯消元法

初等矩阵

定义以下三种矩阵为初等矩阵:

  1. 倍乘矩阵:左上到右下对角线上第 i 个位置为 k(k0),对角线上其它位置全为 1,非对角线位置全为 0 的矩阵:

[100000100000100000k000001]

它只有一个维度扩大了 k 倍,可以发现它的行列式为 k

  1. 对换矩阵:将单位矩阵第 i 排第 i 列的 1 交换到第 j 列,将第 j 排第 j 列的 1 交换到第 i(ij) 得到的矩阵:

[100000010000001000000010000100000001]

这相当于是将两个向量对调了一下位置,此时“平面”发生了翻转,行列式为 1

  1. 倍加矩阵:将单位矩阵第 i 行第 j 列变为 k(k0,ij) 所得到的矩阵:

[1000000100000010000001k0000010000001]

此时会发现某个向量往一个方向平移了一段距离,但是“高”没有发生变化,因此行列式为 1

初等变换

对于一个矩阵,他的初等变换有三种:

  1. 用一个非 0x 乘以矩阵的某一行或某一列,相当于这个矩阵乘以了倍乘矩阵;

  2. 将矩阵某一行或某一列乘以非 0x,并加到另一行或列,相当于这个矩阵乘以了倍加矩阵;

  3. 交换矩阵的某两行或列,相当于这个矩阵乘以了对换矩阵。

这也就是平时我们在求解线性方程组的操作方程。

普通高斯消元法

首先,我们可以通过列空间判断是否有解,通过零空间判断有多少组解,这里只考虑有唯一解的情况。

由于我们知道矩阵与线性方程组有密不可分的关系,因此可以将线性方程组变化成矩阵形式:

[a1,1a1,2a1,nc1a2,1a2,2a2,nc2an,1an,2an,ncn]

其中 A 是系数矩阵,C 是答案矩阵。

普通高斯消元的目标是将 A 消成一个上三角矩阵,也就是只有左上-右下对角线及其上方才有值的矩阵,这样再倒着推就可以出结果。

具体做法是枚举每一列,将第 i 列的第 i 个元素作为主元,将主元下方的第 j 排的数字通过求最小公倍数的方法消去(用 double 可以直接相除,再将主元 i 行乘以这个商再将用 j 排相减)。特别的,如果主元是 0,需要将主元这列不为 0 的行与主元这行交换,如果主元这一列全为 0,此时该方程已经有无数多组解了,已经排除掉了。

接着从下往上递推,由于最后一排一定是 axn=cn 这种形式,于是可以求出 xn,而倒数第 2 排一定是 bxn1+dxn1=cn1,此时又可以把 xn1 求出来。以此类推,就可以求出所有 x 了。

完整代码:洛谷 P3389 【模板】高斯消元法

//懒得写了,我们一般都用高斯-约旦消元法

高斯-约旦消元法

此算法在普通高斯消元的基础上,从后往前将主元上方的数给消掉了,这样只用将 ci 除以 ai 就可以得到答案。

完整代码(来个双倍经验):洛谷 P2455 [SDOI2006] 线性方程组

#include <bits/stdc++.h>
using namespace std;
double a[105][105], eps = 1e-6;
int main(){
	int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n + 1; j++)
            scanf("%lf", &a[i][j]);
    for(int i = 1; i <= n; i++) {
        int m = i;
        for(int j = 1; j <= n; j++){
        	if(fabs(a[j][j]) > eps && j < i)
				continue;
        	if(fabs(a[j][i]) > fabs(a[m][i]))
                m = j;
		} 
        for(int j = 1; j <= n + 1; j++)
            swap(a[i][j], a[m][j]);
        if(abs(a[i][i]) <= eps)
			continue;
        for(int j = 1; j <= n; j++)
            if(j != i){
                double d = a[j][i] / a[i][i];
                for(int k = i; k <= n + 1; k++)
                    a[j][k] -= a[i][k] * d;
            }
    }
    int key = 1;
    for(int i = 1; i <= n; i++)
		if(abs(a[i][i]) <= eps)
			if(abs(a[i][n + 1]) > eps)
				key = -1;
			else if(key != -1)
				key = 0;
	if(key != 1){
		printf("%d", key);
		return 0;
	}
    for(int i = 1; i <= n; i++)
        printf("x%d=%.2lf\n", i, a[i][n + 1] / a[i][i]);
    return 0;
}

下面展示一下高斯消元两个经典应用。

行列式求值

行列式的变换

先来两个行列式特有的变换:

  1. 进行一次矩阵转置,行列式不变;

证明:感性理解。考虑到行列式的表达式在枚举列的排列,每列的每个数都与从另外那几列 每一列随机选出一个数相乘。由于对称性,枚举行依然会枚举到这种排列,两种枚举方式都枚举了所有排列,因此转置一次,行列式不变。

  1. 若矩阵有两行成比例,行列式为 0

证明:首先可以发现,将矩阵转置后,行列式不变,那么问题可以变成矩阵有两列成比例。

这就意味着这两个向量在同一条直线上,不能同时是这个空间的一组基,那么就只剩下 n1 个向量,发生了维度的坍缩,因此行列式为 0

行列式也有初等变换,分别为:

  1. 用一个非 0x 乘以矩阵的某一行或某一列,行列式也乘以 x

证明:根据 det(M1)det(M2)=det(M1M2),由于倍乘矩阵的行列式为 k,那么变换后的行列式就为变换后的 k 倍。

  1. 将矩阵某一行或某一列乘以非 0x,并加到另一行或列,行列式不变;

证明:倍加矩阵的行列式为 1,那么变换后的行列式的值不变。

  1. 交换矩阵的某两行或列,行列式取反。

证明:对换矩阵的行列式为 1,那么变换后的行列式的值取反。

代数余子式

先考虑以下 3×3 矩阵 A[a1,1a1,2a1,3a2,1a2,2a2,3a3,1a3,2a3,3],我们先按照行列式的计算式将其展开为 det([a1,1a1,2a1,30a2,2a2,30a3,2a3,3])+det([0a1,2a1,3a2,1a2,2a2,30a3,2a3,3])+det([0a1,2a1,30a2,2a2,3a3,1a3,2a3,3])

根据矩阵交换两行后,行列式取反,那么原式 =det([a1,1a1,2a1,30a2,2a2,30a3,2a3,3])det([a2,1a2,2a2,30a1,2a1,30a3,2a3,3])+det([a3,1a3,2a3,30a1,2a1,30a2,2a2,3])

根据行列式的计算式,每次要从每列选出行号互不相同的 3 个数,那么如果选了 a1,1,就不会再选 a1,2a1,3;如果选了 a1,2a1,3,那么第一列只会选 a2,1a3,1,而它们都是 0,对答案无贡献。因此只有选 a1,1 时才会有贡献,于是原式可以简化成 a1,1det([a2,2a2,3a3,2a3,3])a2,1det([a1,2a1,3a3,2a3,3])+a3,1det([a1,2a1,3a2,2a2,3]),将行列式降维了。更高维同理。

矩阵也可以按照别的列进行展开,方法类似。

现在我们就有了余子式的定义。在一个 n 阶矩阵中,我们将第 i 行和第 j 列去除得到新的矩阵的行列式为原矩阵的行列式的余子式,记为 Δi,j

det(A) 按每一列展开可以得到 det(A)=a1,1Δ1,1a1,2Δ1,2+a1,3Δ1,3=a2,1Δ2,1+a2,2Δ2,2a2,3Δ2,3=a3,1Δ3,1a3,2Δ3,2+a3,3Δ3,3

有强迫症的人可能旧的正负号很碍眼,于是我们又定义 Δi,j=(1)i+jΔi,j,于是原式又可以写成 det(A)=a1,1Δ1,1+a1,2Δ1,2+a1,3Δ1,3=a2,1Δ2,1+a2,2Δ2,2+a2,3Δ2,3=a3,1Δ3,1+a3,2Δ3,2+a3,3Δ3,3

此时这里的 Δi,j 就叫做原矩阵的行列式的代数余子式,对于一般的矩阵 A,都有 det(A)=i=1nai,jΔi,jj 为任意一列)。

考虑到矩阵转置后行列式不变,那么 det(A)=j=1nai,jΔi,ji 为任意一行)。

行列式求值

考虑到矩阵某一行如果有 ai,j=0,那么它对答案就无法造成贡献,于是考虑能否把以下矩阵:

[a1,1a1,2a1,3a1,na2,1a2,2a3,3a2,na3,1a3,2a3,3a3,nan,1an,2an,3an,n]

通过行列式初等变换变为以下矩阵:

[a1,1a1,2a1,3a1,na2,1a2,2a3,3a2,na3,1a3,2a3,3a3,n000an,n]

那么该矩阵的行列式就可以表达为 an,n×det([a1,1a1,2a1,3a1,na2,1a2,2a3,3a2,na3,1a3,2a3,3a3,nan,1an,2an,3an,n])

对里面的行列式继续初等变换,那么最终答案就变成了 i=1nai,i

注意到洛谷上行列式求值的模板题为任意模数,这时对要消元的两行进行辗转相减法才可以消元。

完整代码:洛谷 P7112 【模板】行列式求值

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 6e2 + 9;
int a[N][N], n, p, ans = 1, neg = 1;
signed main(){
	scanf("%lld%lld", &n, &p);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
			scanf("%lld", &a[i][j]);
	for(int i = 1; i <= n; i++){
		for(int j = i + 1; j <= n; j++){
			while(a[i][i]){
				int d = a[j][i] / a[i][i];
				for(int k = i; k <= n; k++)
					a[j][k] = ((a[j][k] - d * a[i][k]) % p + p) % p;
				for(int k = 1; k <= n; k++)
					swap(a[i][k], a[j][k]);
				neg = -neg;
			}
			for(int k = 1; k <= n; k++)
				swap(a[i][k], a[j][k]);
			neg = -neg;
		}
	}
	for(int i = 1; i <= n; i++)
		ans = ans * a[i][i] % p;
	ans *= neg;
	printf("%lld\n", (ans + p) % p);
	return 0;
}

矩阵求逆

首先,如果一个矩阵的行列式为 0,那么矩阵就没有逆,因为这个变换相当于把一个高维空间压缩到了更低的维度,而我们无法将一个低维空间变换成高维空间,因为此时一个向量可能对应多个向量(就像你可以想象二维空间长什么样,而想象不出四维空间长什么样)。

考虑可逆矩阵 A,它的逆为 xAX=I,将 I 拆开变成 (e1,e2,,en)n 个只有 i 位置为 1 的向量,将 X 拆开变成 (x1,x2,,xn)n 个向量,于是现在我们要求 一个线性方程组:

Ax1=e1Ax2=e2Axn=en

将每一行展开,都可以得到一个线性方程组,套用高斯-约旦消元法的解法,如果我们能做到以下变换:

(A|e1)(I|s1)(A|e2)(I|s2)(A|en)(I|sn)

那么 X=S,将这个线性方程组综合起来,就变成了 (A|I)(I|X) 的变换。于是只用将单位矩阵拼在一个可逆矩阵后面,将该可逆矩阵消成单位矩阵,那么拼在后面的单位矩阵就变成了这个可逆矩阵的逆。

完整代码:洛谷 P4783 【模板】矩阵求逆

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 409, MOD = 1e9 + 7;
int a[N][N << 1], n;
int extend_gcd(int a, int b, int &x, int &y){
	if(b == 0){
		x = 1;
		y = 0;
		return 0;
	}
	int d = extend_gcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int mod_inverse(int a, int m){
	int x, y;
	extend_gcd(a, m, x, y);
	return (x % m + m) % m;
}
signed main(){
	scanf("%lld", &n);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++)
			scanf("%lld", &a[i][j]);
		a[i][i + n] = 1;
	}
	for(int i = 1; i <= n; i++){
		int maxx = i;
		for(int j = i + 1; j <= n; j++)
			if(a[j][i] > a[maxx][i])
				maxx = j;
		for(int j = 1; j <= 2 * n; j++)
			swap(a[i][j], a[maxx][j]);
		if(a[i][i] == 0){
			printf("No Solution");
			return 0;
		}
		int inv = mod_inverse(a[i][i], MOD);
		for(int j = 1; j <= n; j++){
			if(j != i){
				int tmp = a[j][i] * inv % MOD;
				for(int k = i; k <= n * 2; k++)
					a[j][k] = ((a[j][k] - a[i][k] * tmp) % MOD + MOD) % MOD;
			}
		}
		for(int j = 1; j <= n * 2; j++)
			a[i][j] = a[i][j] * inv % MOD;
	}
	for(int i = 1; i <= n; i++){
		for(int j = n + 1; j <= 2 * n; j++)
			printf("%lld ", a[i][j]);
		printf("\n");
	}
	return 0;
}

参考资料

posted @   JPGOJCZX  阅读(24)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示