Cayley-Hamilton 定理学习笔记
CH 定理主要用于优化线性递推。
下面很多东西都是自己瞎琢磨的,大概错漏挺多。
线代的一些基本知识
感觉学习 CH 困难的很大一部分原因就是缺少一些线代的基础。
- 矩阵的秩 \(r(A)<n\),说明向量组线性相关,说明行列式 \(|A|=0\)。反之,如果 \(|A|\neq 0\),那么矩阵满秩。即二者充要。
证明:考虑行列式的性质即可,由于把第 \(i\) 行的线性组合加到第 \(j\) 行不影响行列式的计算结果,所以如果线性相关,那么说明可以把某一行全部消掉,那么自然不满秩。
- 对于一个 \(n\) 元齐次线性方程组 \(Ax=0\),其中 \(A\) 是一个 \(n\times n\) 的矩阵,\(x\) 是一个由未知量构成的长度为 \(n\) 的列向量。其存在非全零解的条件是系数行列式 \(|A|=0\)
证明:当 \(|A|\neq 0\) 时,矩阵满秩,感性理解一下,x 就只能是全零了。理性证明的话,考虑把 \(A\) 消成上三角矩阵之后,可以解出 \(xn=0\),进一步 \(x_{n-1}\) 也会解出是 \(0\)
- 特征值和特征向量
定义:
对于一个 \(n\) 阶方阵 \(A\) ,若非 \(0\) 列向量 \(\vec{v}\) 和 \(\lambda \in C\) 满足 \(A\vec{v} = \lambda\vec{v}\)
那么称 \(\vec{v}\) 为特征值 \(\lambda\) 的特征向量
理解:
展开式子,有
若把矩阵的每一行理解为一个基向量 \(\varepsilon_i\),则是表示基向量与该向量的内积 \((\varepsilon_i \cdot \vec{v})\)等于 \(\lambda \vec{v}\)
所以我们可以把矩阵 \(A\) 视作在 \(n\) 维空间中,对 \(\vec{v}\) 在 \(n\) 个方向上进行不同程度的拉伸,这个拉伸的过程还可能伴随着旋转,因为如若把 \(\vec{v}\) 视作基,即可以认为是掺杂了其他的基
而这个特征向量特殊的地方就在于,他真的就可以当做是基(即它是空间中的一些特殊的向量,使得 \(A\) 对这些向量施加变化的时候只有拉伸作用)
也就是说,矩阵 \(A\) 对应的 \(n\) 维空间的 \(n\) 个方向就是 \(n\) 个不同的特征向量所指的方向
此时,这个基构成 \(n\) 维空间 \(S\) ,设 \(\vec{v}\) 在空间 \(S\) 中的表示为 \(\vec{a}\) (投影??)
此时,\(\vec v\) 和 \(\vec a\) 表示的完完全全的一个向量。只是说采用的基底不同
此时,我们计算 \(A \vec a\) ,相当于对 \(\vec a\) 在 \(S\) 的各个方向上进行了不同程度的拉伸变换(没有旋转了!)。程度的不同体现在 特征值 的不同,特征向量 \(\vec v\) 对应的特征值 \(λ\) 越大,那么 矩阵 \(A\) 在 \(\vec v\) 所表示的方向上对 \(\vec a\) 的拉伸作用越明显。
具体的,\(λ > 1\), 拉伸;\(λ = 1\), 不变;\(λ < 1\), 压缩
我们知道,向量是不能做除法的,但在特征方向上,就能除了,因为在两个向量共线时,它们所有的对应分量的比值都相同,而不共线时,比值就不同,就没法除了。
或参见:所有方阵都有特征值吗?如何证明? - 天下无难课的回答 - 知乎
- 矩阵 \(A\) 在复数集内有 \(n\) 个特征值。
证明:考虑特征值的定义,对于 \(\lambda\),它是通过一个特征向量来定义的,即二者大概是 1-1 对应,所以只需要证明有 \(n\) 个特征向量。
考虑 \(\lambda x=A x\),等价于 \(|\lambda E-A|x=0\),(这是我们熟知的特征多项式的形式)即证明它有 \(n\) 个解。
所以 \(|\lambda E-A|=0\),这是上方已经证明的结论。
考虑这个行列式,它可以写成 \(\lambda\) 为未知数的一元n次方程组,而 n次方程组在复数集内一共有n个解,(如果我们观察上式,可以发现 \(\lambda\) 只出现在正对角线上,显然A的特征值就是方程组的解)同时,在对应 \(\lambda\) 的条件下,可以解出 \(x\),即特征向量。
这个结论说明了,上方所提到了 “这个基构成 \(n\) 维空间 \(S\) ”,确实是一个 \(n\) 维度的空间。
同时这个其实可以看做是 CH 定理应用逻辑的一部分。
- 特征多项式
设 \(A\) 是一个 \(k\) 阶矩阵,则其特征多项式为 \(f(x)=|x E-A|=x^{k}+c_1x^{k-1}+c_2x^{k-2}+\dots+c_k\) ,其中 \(E\) 是单位矩阵, \(c\) 是一系列系数,\(x\) 可以取很多种值,如其可以取为一个矩阵或一个数,注意当它取成一个矩阵的时候 \(f(x)\) 也是一个矩阵
特征行列式和特征多项式其实指的是一个东西。
- 两个 \(n\) 阶多项式,如果有共同的 \(n\) 个零点,那么他们对应项的系数一定相同。
其重点在于,可能两个多项式的主元并不相同,但是系数可以一致。(\(f(X)\) 和 \(f(y)\) 之类)
证明考虑回忆一下拉格朗日插值的原理之类之类。
- 特征根方程
在高中阶段,我们学习到这样一种求解线性递推数列的方法:
对于 \(k\) 阶线性递推数列 \(a_n=\sum_{i=1}^k c_i a_{n-i}\),其特征根方程是 \(x^k=\sum_{i=1}^k c_i x^{k-i}\)
有这样一个结论,假如对于这个特征根方程有 \(k\) 个不同的解,即特征根 \(\lambda_1,\lambda_2,\dots \lambda_k\) 无重根。
首先\(a_n=\lambda_i^n\) 肯定是上述递推式的解。
其次,由于是线性递推,所以在不考虑初值的情况下,它的几个解的线性组合仍然是它的解。即通解是 \(a_n=\sum_i^k \alpha_i \lambda_i^n\)
然后,只需要带入 \(a_{1\dots k}\) 的初值,就可以得到 \(k\) 个方程,并解出 \(\alpha_{1\dots k}\),于是就能得到数列 \(a_n\) 的通项公式。
扩展:注意到这里是一定可以解出这 \(k\) 个方程的,因为这个关于 \(\alpha_{1\dots k}\) 的方程的系数矩阵的行列式写出来是范德蒙德矩阵
这个技巧常常用于秒杀一类高中数列题
再扩展:假如有重根怎么办?留作思考/hsh
- 线性递推的转移矩阵的特征多项式就是特征根方程
(贺的官老师的)
假设一数列 \(h\) 满足 \(h_n (n\ge k)\) ,\(h_n = \sum_{i = 1}^ k a_i h_{n - i}\),转移矩阵 \(M\) 的特征多项式为 $f(\lambda) $。
对于 \(f(\lambda)\)
设 \(F_k(x) = \sum _{i = 0}^k c_{k,i} x^i\) 表示取其最后 \(k\) 行 \(k\) 列的主子式的特征多项式
考虑计算行列式 ,如若第一行选择 \(\lambda\) ,那么有贡献 \(\lambda F_{k - 1}(\lambda)\)
如若第一行选择 \(-1\) ,容易发现前 \(k-1\) 行选择的都是 \(-1\) ,最后一行会选择 \(-a_k\) ,此时逆序数 \({k-1}\) 个
所以贡献为 \((-1)^{k-1} · - a_k (-1)^{k-1}=-a_k\),即 \(c_{k,0} = -a_k\)
考虑第一种贡献方式,有 \(c_{k,i} = c_{k-1,i-1} = \dots = c_{k-i,0} = -a_{k-i}\)
所以,\(F_k(x) = x^k - \sum _{i =0}^{k-1}a_{k-i}x^i\) ,我们知道 \(F_k(x) =f(x)\)
而这个时候,如果我们对比一下 \(f\) 和我们的特征根方程,很容易发现他们是一样的!!!
那么进一步的,实际上,如果我们求出了特征多项式,那么我们实际上就获得了线性递推式。(这可能听起来有点鸡肋,我既然已经知道特征多项式,怎么会不知道原递推式呢?但是在下方的例题中展现出了威力)
Cayley-Hamilton定理
下面的等式在方阵 \(A\) 满秩时恒成立:
其中 \(\lambda_k\) 是 \(A\) 的第 \(k\) 个特征值。
魔法地,注意到 \(G(A)=\prod_k(\lambda_k E-A)\) 和 \(f(\lambda)=|\lambda E-A|\) 的零点相同(\(\lambda_{1\dots k}\)),项数也相同,虽然说主元不一样,但是对应的多项式系数就是一样的。(上方基础知识里面讲过)
所以我们可以把上面的定理等价成为 \(f(A)=\mathcal{O}\)
加速矩阵快速幂
比如,我们要求 \(M ^n\) ,\(M\) 是一个 \(k\times k\) 的矩阵,平凡的,我们的复杂度是 \(k^3\log n\) ,但是当 \(k\) 比较大的时候会被卡掉。这里有一种优化到 \(O(k^3)-O(k\log k\log n)\) 的方法
具体地,我们可以采用 这篇博客 中的方法先 \(O(k^3)\) 求出 \(M\) 的特征多项式 \(f(\lambda) =|\lambda E-M|\)
由于 \(f(M) = 0\) ,所以 \(M^n = M^n \bmod {f(M)}\)
设 \(g(x) = x^n\) , \(g(x) = h(x)f(x)+r(x)\) ,由于 \(f(x)=0\),所以 \(g(x) = r(x)\)
此时,我们完成了这样的一件事情:将矩阵的幂化成多项式的取模幂
即,设 \(\lambda ^n \bmod f(\lambda) = \sum _{i = 0}^{k - 1} a_i \lambda^{i}\) ,将 \(\lambda = M\) 带入,需要计算 \(\sum_{i = 0}^{k - 1}a_i M^{i}\)
所以,我们考虑平时做模意义下整数快速幂的方法,将整数乘法换成多项式乘法,整数取模换成多项式取模,就可以得到 \(a_i\) ,这部分的复杂度是 暴力乘模 \(O(k^2 log n)\) / NTT \(O(k\log k\log n)\)
最后需要带入 \(M^{i = 0\dots k -1}\) ,可以考虑直接 \(O(k^4)\) 爆算,在齐次线性递推的背景下,就是初值的意思
加速线性递推
我们想要求出 \(h_n (n\ge k)\) ,\(h_n = \sum_{i = 1}^ k a_i h_{n - i}\)
设这个转移矩阵 \(M\) 的特征多项式为 $f(\lambda) $ ,\(g(\lambda) = \lambda^n\)
设初值为列向量 \(B\) ,第 \(i\) 行为 \(h_{i - 1}\) ,即求 \(BM^n[0]\)
根据上方讲过的知识,我们可以知道 \(f(x) = x^k - \sum _{i =0}^{k-1}a_{k-i}x^i\)
求解出 \(f\) 之后,我们只需要求解 \(g(x) \bmod f(x)\) ,得到 \((g \mod f)(x)=\sum n_i x^i\)
回到原问题,我们求解的是 \(BM^n[0] = \sum_{i = 0}^{k-1} n_iBM^i[0]\) ,又有 \(BM^{i}[0]=h_i (i<k)\)
好活,这样的话,我们一旦知道 \(h_{0\dots k-1}\) 那么就可以知道 \(BM^n[0]\) ,若想要知道 \(BM\) 这个列向量的所有值,我们也只需要知道 \(h_{0\dots 2k-1}\)
若只求单个元素,总时间复杂度是求 \(M^n\) 时的 \(O(k^2logn)/O(klogklogn)\)。
若要求整个列向量,要么直接用 \(∑_{i=0}^{k^1}n_iBM^i\) 这个式子暴力矩阵乘法,时间复杂度 \(O(k^2\log n+k^3)\),比直接矩阵快速幂少个 \(\log\) 或者 \(k\),且这样我们就只需要知道 \(h_0,h_1,\dots,h_{k?1}\);
要么挨个元素去算,时间复杂度 \(O(k^3logn)/O(k^2logklogn)\)。可以发现不用 \(NTT\) 和 \(k \log k\) 取模的时间和普通矩快一样,用了的则是把 \(k\) 变成了 \(\log k\)
——gtr
多项式取模——听起来一个很奇怪的操作,凭啥会突然想到干这样一件事情呢?
考察我们如何求 \(fib_n\),\(fib_i=fib_{i-1}+fib_{i-2}\)
OI 的方法是,把转移矩阵搞出来,然后搞矩阵 \(n\) 次幂啥的。
小学的方法是暴力拆,例如对于 \(n=5\):\(fib_5=fib_4+fib_3=2fib_3+fib_2=3fib_2+2fib_1=5fib_1+3fib_0\)
考察这个过程,其实就是每次找到等式中最大的一项,然后根据递推式把它变成更小的项。
上述过程,就是通过每次消去最高次项,求 \(x^5\) 对斐波那契数列的特征多项式 \(x^2-x-1\) 取模的结果的过程
特征多项式,也就是特征根方程,每次我们其实是将一个 \(x^{2}\) 代换为 \(x+1\),其实也就是 减去 \((x^2-x-1)\) 的若干倍,那么为了代换到底,这个若干倍要尽可能抵到顶,那么实际上做的就是取模。
即,「多项式取模」的过程恰好与「每次按递推式展开下标最大的那项」的过程一致,都可以用「替换」来解读。所以我们直接对 \(x^n\) 做多项式取模,就可以得到 \(a_n=\sum_{i=1}^k c_ia_i\) 这类形式。
例 1 「BZOJ4161」Shlw loves matrixI
模版题
#include <bits/stdc++.h>
using namespace std;
const int N = 4444;
const int mod = 1e9 + 7;
char buf[1 << 23], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : * p1 ++)
int read() {
int s = 0, w = 1; char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') w = -1;
ch = getchar();
}
while(isdigit(ch)) {
s = s * 10 + (ch ^ 48),
ch = getchar();
}
return s * w;
}
void inc(int& a, int b) { a = a >= mod - b ? a - mod + b : a + b; }
void dec(int& a, int b) { a = a >= b ? a - b : a + mod - b; }
int add(int a, int b) { return a >= mod - b ? a - mod + b : a + b; }
int del(int a, int b) { return a >= b ? a - b : a + mod - b; }
int mul(int a, int b) { return 1ll * a * b % mod; }
struct vec {
int a[N];
void clear() {
memset(a, 0, sizeof(a));
}
}a, b, P, e;
int n, k, ans;
vec mul(vec a, vec b, vec P) {
vec ans; ans.clear();
for(int i = 0; i <= k; ++ i)
for(int j = 0; j <= k; ++ j)
inc(ans.a[i + j], mul(a.a[i], b.a[j]));
for(int i = k << 1; i >= k; -- i) if(ans.a[i])
for(int j = 0, tmp = ans.a[i]; j <= k; ++ j)
dec(ans.a[i - j], mul(tmp, P.a[k - j]));
return ans;
}
vec exp(vec a, int b, vec P) {
vec ans = e;
while(b) {
if(b & 1) ans = mul(ans, a, P);
a = mul(a, a, P), b >>= 1;
}
return ans;
}
signed main() {
e.a[0] = 1;
n = read(), k = read();
for(int i = 1; i <= k; ++ i)
a.a[i] = read(), P.a[k - i] = del(0, a.a[i]);
for(int i = 1; i <= k; ++ i)
b.a[i - 1] = (read() + mod) % mod;
P.a[k] = 1, a.clear(), a.a[1] = 1;
a = exp(a, n, P);
for(int i = 0; i < k; ++ i)
inc(ans, mul(b.a[i], a.a[i]));
printf("%d\n", ans);
return 0;
}
例 2 20240122 序列(sequence)
首先经过一通分析,可以得到这样的 DP 柿子:
初值 \(f_{0,0}=1\),求 \(f_{n,0}\)
考虑这个咋做。
solution1(std):
如果将 \(f_i\) 这个向量看做一个整体,那么可以写出一个 \(k\times k\) 的转移矩阵 \(M\)(这也许可以叫做一阶线性递推把)
仔细观察转移式子,可以感性理解到对于每一个 \(j\) 而言,\(F_j(i)\) 是一个最高 \(k+1\) 阶线性递推序列(因为状态之间转移的时候没有对状态本身的平方,即类似 \(f^2\) 的项,所以都是线性关系)
考虑抽出 \(F_0(i)\) 来看,而把其他都看成是辅助数组,即把 \(F_{j\neq 0}(i)\) 的位置都看成是由 \(F_0\) 中的某些项线性组合得到的东西。
那么,这个时候,我们的转移矩阵 \(M\) 也可以理解是,\(F_0(i)\) 本身的线性递推转移矩阵 经过非常复杂的线性变化得到的。注意到一个重要性质:特征多项式在基变更下不变。
那么,根据之前的结论,只需要求 \(M\) 的特征多项式(根据 Cayley-Hamilton 定理,\(M\) 的特征多项式 \(f(M)=0\)),就可以得到 \(F_0(i)\) 的递推公式了。
非常厉害吧!
考察特征多项式:
设 \(n+1\) 阶上述多项式是 \(d_n\)
若最后一行取右下角,那么 \((2n+1-x)d_{n-1}\)
若最后一行取 \(n^2\),那么倒数第二行只能取 \(1\),有 \(n^2d_{n-2}\)
所以有递推关系 \(d_n=(2n+1-x)d_{n-1}+n^2d_{n-2}\)
其对应的递推矩阵:
设 \(M_n=\begin{bmatrix} &2n+1-x,&1\\ &-n^2,&0\\ \end{bmatrix}\),那么 \(d_n=[0][0]\prod_{i=0}^n M_i\)
现在需要干的事情就是把 \(\prod_{i=0}^k M_i\) 处理出来,其得到的结果矩阵 \(res\) 的第一行第一列就是我们要的特征多项式 \(f\)
为了避免多项式次数不平衡,这个的方法是考虑分治处理,复杂度 \(T(k)=2T(k/2)+k\log k=k\log^2 k\)
获得多项式 \(f\) 之后,可以直接套 \(x^n\) 取模的方法解决。但是为了复杂度,需要写一长串的多项式除法之类,非常麻烦,有一种小清新的算法是 波斯坦-茉莉算法。
solution2(jsy):
非常 powerful,注意到既然 \(F_0(i)\) 是线性递推数列,且 \(F_{j\neq 0}\) 其实都是 \(F_0\) 的一种线性组合。那么不妨尝试直接通过递推关系把 \(F_0\) 解出来。
知 \(F_j(x)=\sum_{i>0} f_{i,j}x^i\),由 DP 式子可以得到:
\(F_k\) 可以表达为 \(\dfrac{P_k(x)}{Q_k(x)} F_{k-1}\) 的样子。具体的,\(F_k=\dfrac{x}{1-x(2k+1)}F_{k-1}\)
那么进一步,将 \(F_{i}=\dfrac{P_i(x)}{Q_i(x)} F_{i-1}\) 带入 \(F_{i-1}(x)\) 的式子里面,可以得到 \(P_{i-1}\) 和 \(Q_{i-1}\)
代入的过程中肯定没有 \(P\) 和 \(Q\) 相互的乘除,分子分母在代入下一个方程后会变成原来的分子分母的(类似)线性组合,肯定能拿矩阵来转移.(类似于树上主元法的时候的操作过程)
具体的:
把中间的矩阵和 solution1 一样分治乘起来,就可以通过 \(P_k,Q_k\) 得到 \(P_1,Q_1\)。
那么这样就可以直接解 \(F_0\) 了,带进去会是一个求 \([x^n]\dfrac{P(x)}{Q(x)}\) 的形式,其中 \(deg(P)=k,deg(Q)=k+1\),完美符合 波斯坦-茉莉 算法的要求,那么其实也可以通过 波-茉 的思路,把实际的递推式搞出来。
波斯坦-茉莉算法
正经的讲解 波斯坦-茉莉算法
时间关系,这里只记录一个关键的关系。
注意到对于给定一个线性递推 \(a_i=\sum_{j=1}^d c_ja_{i-j}\) 之后,对于 \(Q(x)\) 的构造是 \(Q(x)=1-\sum_{i=1}^d c_ix^i\)
和特征多项式/特征根方程进行对比,发现多项式 \(Q_R\) 就是特征多项式。