1. 高斯消元
定义以下三种操作为 初等行变换 :
- 将某一行乘上 \(c\)
- 将某一行的 \(c\) 倍加到另一行
- 交换两行
这里介绍高斯-约旦消元法,利用初等行变换将矩阵转化成对角矩阵,然后就很容易求出解。
- 枚举 \(i\) 表示现在要消去第 \(i\) 项
- 在 \([i,n]\) 内选择一个 \(k\) 满足 \(a[k][i]\neq 0\),若不存在则说明无解
- 交换第 \(i\) 行和第 \(k\) 行
- 对于 \([i+1,n]\) 内所有 \(j\),将第 \(j\) 行减去 \(\dfrac{a[j][i]}{a[i][i]}\) 倍的第 \(i\) 行
容易发现,最后第 \(i\) 项系数只会出现在第 \(i\) 行。并且任意时刻,第 \(i\) 行的 \(1\dots i-1\) 项系数都为 \(0\),所以不会对其他方程的这些项造成影响。
inline bool zero(double t) {
return fabs(t) < 1e-8;
}
signed main() {
scanf("%d", &n);
rep(i, 1, n) rep(j, 1, n + 1)
scanf("%lf", &a[i][j]);
rep(i, 1, n) {
k = i;
if(zero(a[i][i])) rep(j, i + 1, n) if(!zero(a[j][i])) { k = j; break; }
if(i ^ k) rep(j, i, n + 1) swap(a[i][j], a[k][j]);
if(zero(a[i][i])) return puts("No Solution") & 0;
rep(j, 1, n) if(i != j) {
t = a[j][i] / a[i][i];
rep(k, i, n + 1) a[j][k] -= a[i][k] * t;
}
}
rep(i, 1, n) printf("%.2lf\n", a[i][n + 1] / a[i][i]);
return 0;
}
2. 行列式
对于一个方阵,其行列式是线性变换的伸缩因子。例如在三维空间中,行列式表示线性变换造成的体积放大倍率。
代数定义:\(\det A=|A|=\sum\limits_{p是n的排列}(-1)^{p的逆序对数}\prod\limits_{i=1}^na_{i,p_i}\)
看右边那串东西,其实是从方阵中选出 \(n\) 个数,使得没有两个数在同一行或同一列,然后把它们乘起来。所以如果选出的数中有一个 \(0\) 就没有贡献了。
排列的性质
- 交换两数,逆序对数的奇偶性改变
- \(n\ge 2\) 时,奇偶排列(逆序对数为奇 / 偶数的排列)各占一半
行列式的性质
- \(|M|=|M^T|\)
- 交换两行,行列式反号
- 用一个数乘行列式的某行,等于用这个数乘此行列式
- 如果行列式中有两行成比例,则行列式等于 \(0\)(先把比例系数提出来,发现两行相同了,交换两行行列式不变。但根据性质 2,行列式反号了,所以只能为 \(0\))
- 若行列式中某一行是两组数之和,则这个行列式等于两个行列式之和,这两个行列式分别以这两组数为该行,而其余各行与原行列式对应各行相同。即:\(\left|\begin{matrix}\dots\\a_{i,1}+b_{i,1},a_{i,2}+b_{i,2}\dots a_{i,n}+b_{i,n}\\\dots\end{matrix}\right|=\left|\begin{matrix}\dots\\a_{i,1},a_{i,2}\dots a_{i,n}\\\dots\end{matrix}\right|+\left|\begin{matrix}\dots\\b_{i,1},b_{i,2}\dots b_{i,n}\\\dots\end{matrix}\right|\)
- 将某一行的倍数加到另一行,行列式的值不变
- \(\left|\begin{matrix}A~0\\C~B\end{matrix}\right|=|A||B|\),其中 \(A,B\) 是方阵 (从选数的角度考虑)
- 一个矩阵的行列式可以按行展开,即:\(\left|\begin{matrix}\dots\\a_{i,1},a_{i,2}\dots a_{i,n}\\\dots\end{matrix}\right|=\left|\begin{matrix}\dots\\a_{i,1},0\dots 0\\\dots\end{matrix}\right|+\left|\begin{matrix}\dots\\0,a_{i,2},0\dots 0\\\dots\end{matrix}\right|+\dots+\left|\begin{matrix}\dots\\0,\dots 0,a_{i,n}\\\dots\end{matrix}\right|\)(还是从选数的角度理解,第 \(i\) 行只能选一个数,选 \(a_{i,1},a_{i,2},\dots,a_{i,n}\) 的方案是独立的)。进一步地,定义代数余子式:\(A_{i,j}=(-1)^{i+j}|M_{i,j}|\),其中 \(M_{i,j}\) 是原矩阵删去第 \(i\) 行和第 \(j\) 列得到的矩阵。一个矩阵的行列式,等于它某一行中,每个元素乘上它的代数余子式的和。即:\(|B|=\sum\limits_{j=1}^nb_{i,j}A_{i,j}=(-1)^{i+j}b_{i,j}|M_{i,j}|\)(展开后,每个矩阵的第 \(i\) 行只有一个数,用 \(i+j-2\) 次交换邻行邻列把它交换到 \((1,1)\),然后用性质 7)
- 上(下)三角矩阵的行列式为其主对角线元素之积。
行列式求值
现在考虑如何计算一个矩阵的行列式。观察到第 9 条性质,我们可以把矩阵消成上三角矩阵,同时注意符号的变化,然后直接得出答案。
但当矩阵内都是整数时,仍会涉及实数运算,导致精度较低。考虑一种辗转相除的方法:每次选定两行 \(i,j\),将第 \(j\) 行尽可能多地减去第 \(i\) 行,然后交换这两行,直到 \(a_{i,i}=0\)。这样时间复杂度是 \(O(n^3+n^2\log V)\) 的。
rep(i, 1, n) rep(j, i + 1, n) {
while(a[i][i]) {
t = a[j][i] / a[i][i];
rep(k, i, n) a[j][k] = (a[j][k] - a[i][k] * t % p + p) % p;
swap(a[i], a[j]);
ans = -ans;
}
swap(a[i], a[j]);
ans = -ans;
}
if(ans < 0) ans += p;
rep(i, 1, n) (ans *= a[i][i]) %= p;
3. 矩阵求逆
对于矩阵 \(A\),若存在矩阵 \(B\) 使得 \(AB=I\)(\(I\) 是单位矩阵),则 \(B\) 是 \(A\) 的逆,也可写作 \(A^{-1}\)。
对矩阵进行高斯消元时,我们发现可以仅用初等行变换将矩阵变成单位矩阵,而每一种初等行变换都可以表示成一次矩阵乘法。即 \(AP_1P_2\dots P_k=I\)。那么 \(P_1P_2\dots P_k\) 不就是答案吗?
我们可以在高斯消元时记录下所有操作,然后搞一个单位矩阵,再把这些操作做一遍。或者更方便地,我们在原矩阵右边添加一个单位矩阵,把左边消成单位矩阵,右边就求出了逆。
若原矩阵不能消成单位矩阵,则原矩阵不可逆。
n = read();
rep(i, 1, n) rep(j, 1, n) a[i][j] = read();
rep(i, 1, n) a[i][i + n] = 1;
rep(i, 1, n) {
if(!a[i][i]) rep(j, i + 1, n) if(a[j][i]) { swap(a[i], a[j]); break; }
if(!a[i][i]) return puts("No Solution") & 0;
tmp = poww(a[i][i]);
rep(j, 1, n) if(i != j) {
t = a[j][i] * tmp % P;
rep(k, 1, n * 2) a[j][k] = (a[j][k] - a[i][k] * t % P + P) % P;
}
rep(j, i, n * 2) (a[i][j] *= tmp) %= P;
}
rep(i, 1, n) {
rep(j, n + 1, n * 2)
printf("%lld ", a[i][j]);
putchar(10);
}
4. 特征值与特征向量
有时,对一个向量进行一次线性变换(左乘一个矩阵),这个向量方向不变,只是长度发生了变化,即 \(Av=\lambda v\)。也可以表示成 \((A-\lambda I)v=0\)
对于一个矩阵 \(A\),若存在数 \(\lambda\) 和向量 \(v\) 使得 \(Av=\lambda v\),称 \(\lambda\) 为矩阵 \(A\) 的 特征值,\(v\) 为 特征向量。同时定义 \(f(x)=|xI-A|\) 为 特征多项式,\(|xI-A|=0\) 为 特征方程。
定理 \(n\) 阶矩阵 \(A\) 的 \(n\) 个特征值,为其特征方程的 \(n\) 个根。特征值 \(\lambda\) 对应的特征向量为 \((\lambda I-A)x=0\) 的非零解。
注意一个特征值对应无穷多个特征向量,因为可以对向量进行数乘,仍满足定义。
矩阵对角化
相似矩阵:对于两个矩阵 \(A,B\),若存在可逆矩阵 \(P\) 使得 \(P^{-1}AP=B\),则 \(A,B\) 相似。
考虑矩阵 \(A\) 和对角矩阵 \(\Lambda=\left[\begin{matrix}\lambda_1\\&\lambda_2\\&&\ddots\\&&&\lambda_n\end{matrix}\right]\) 相似的条件。设矩阵 \(P\) 由 \(n\) 个列向量构成即 \(P=[p_1,p_2,\dots,p_n]\),且 \(P\) 可逆
则 \(A[p_1,p_2,\dots,p_n]=[p_1,p_2,\dots,p_n]\Lambda\) 即 \([Ap_1,Ap_2,\dots,Ap_n]=[\lambda_1p_1,\lambda_2p_2,\dots,\lambda_np_n]\)。
那么 \(Ap_i=\lambda_ip_i\)。这不就是特征值和特征向量的定义吗?
于是有 \(A=P\Lambda P^{-1}\),其中 \(\Lambda\) 是由 \(A\) 的所有特征值构成的对角矩阵,\(P\) 的第 \(i\) 列是特征值 \(\lambda_i\) 对应的特征向量。
于是我们可以快速计算一些矩阵的幂,\(A^n=(P\Lambda P^{-1})^n=P\Lambda^nP^{-1}\)。
递推数列通项公式
这里只讨论二阶齐次线性递推数列。
首先我们可以直接用上文的结论。比如斐波纳契转移矩阵 \(\left[\begin{matrix}1~1\\1~0\end{matrix}\right]\),特征方程 \(\left|\begin{matrix}\lambda-1~-1\\-1~\lambda\end{matrix}\right|=0\) 即 \(\lambda^2-\lambda-1=0\),解得 \(\lambda=\dfrac{1\pm\sqrt 5}{2}\),并分别求出特征向量 \([1,\dfrac{\sqrt 5-1}{2}]^T,[1,-\dfrac{\sqrt 5+1}{2}]^T\)。然后一顿爆算求出 \(P\) 的逆 \(\left[\begin{matrix}\dfrac{5+\sqrt5}{10}~\dfrac{\sqrt5}{5}\\\dfrac{5-\sqrt5}{10}~-\dfrac{\sqrt5}{5}\end{matrix}\right]\)。把 \(P,\Lambda^n,P^{-1}\) 依次乘起来,最后乘上 \([1,0]^T\),就可得出通项公式 \(f_n=\dfrac{\sqrt 5}{5}\left[(\dfrac{1+\sqrt5}{2})^n-(\dfrac{1-\sqrt5}{2})^n\right]\)。
这么麻烦,为什么不用生成函数呢
考虑一个数列 \(f_i=pf_{i-1}+qf_{i-2}\),其中 \(f_0,f_1\) 已知
尝试构造等比数列:设存在 \(a,b\) 使得 \(f_{i+1}-af_i=b(f_i-af_{i-1})\),即 \(f_{i+1}=(a+b)f_i-abf_{i-1}\)
发现 \(a,b\) 是方程 \(x^2-px-q=0\) 的根。而这个方程就是转移矩阵的特征方程
所以 \(\{f_{i+1}-af_i\}\) 是公比为 \(b\) 的等比数列,有 \(f_{i+1}-af_i=b^i(f_1-af_0)\)。
又发现 \(a,b\) 也满足 \(f_{i+1}-bf_i=a(f_i-bf_{i-1})\)
所以 \(\{f_{i+1}-bf_i\}\) 是公比为 \(a\) 的等差数列,有 \(f_{i+1}-bf_i=a^i(f_1-bf_0)\)。
两式相减,得到 \(f_i=\dfrac{f_1-af_0}{b-a}b^i+\dfrac{f_1-bf_0}{a-b}a^i\)
其实并不用每次推。可以设 \(f_i=\alpha a^i+\beta b^i\),解一个方程就完了
比如斐波纳契数列,\(a=\dfrac{1+\sqrt 5}{2},b=\dfrac{1-\sqrt 5}{2}\),设 \(f_i=\alpha a^i+\beta b^i\),则 \(\begin{cases}f_0=\alpha +\beta=0\\f_1=\frac{1+\sqrt 5}{2}\alpha+\frac{1-\sqrt 5}{2}\beta\end{cases}\)
解得 \(\alpha=\dfrac{\sqrt 5}{5},\beta=-\dfrac{\sqrt 5}{5}\),那么 \(f_n=\dfrac{\sqrt 5}{5}\left[(\dfrac{1+\sqrt5}{2})^n-(\dfrac{1-\sqrt5}{2})^n\right]\)。
对于更高阶的情况,记个结论:设 \(f_i=\alpha a^i+\beta b^i+\gamma c^i+\dots\),列方程组
常系数齐次线性递推
首先可以写出一个 \(k\times k\) 的转移矩阵:\(A=\left[\begin{matrix}f_1~f_2~f_3~\dots f_{k-1}~f_k\\1~~~0~~~0~~\dots~~~0~~~0\\0~~~1~~~0~~\dots~~~0~~~0\\0~~~0~~~1~~\dots~~~0~~~0\\\vdots~~~\vdots~~~\vdots~~~\ddots~~~\vdots~~~\vdots\\0~~~0~~~0~~\dots~~~1~~~0\end{matrix}\right]\)
初始矩阵 \(B=[a_{k-1},a_{k-2},\dots,a_0]^T\),答案是 \((A^nB)_k\)。
凯莱-哈密顿定理:对于矩阵 \(A\),\(f(x)=|xI-A|\) 为其特征多项式,有 \(f(A)=O\),其中 \(O\) 是零矩阵。
本题中,可以用行列式性质 7 和 8 计算出 \(f(x)=x^k-f_1x^{k-1}-f_2^{k-2}-\dots-f_k\)
那么只需计算 \(g(x)=x^n\bmod f(x)=\sum\limits_{i=0}^{k-1}g_ix^i\),答案为 \((g(A)B)_k=(\sum\limits_{i=0}^{k-1}g_iA^iB)_k=\sum\limits_{i=0}^{k-1}g_i(A^iB)_k=\sum\limits_{i=0}^{k-1}g_ia_i\)。
计算 \(g(x)\) 可以用倍增 + 多项式取模。时间复杂度 \(O(k\log k\log n)\)。