目录
- 前置知识
- 矩阵的运算
- 矩阵加法
- 矩阵乘法
- 向量的特殊说明
- 矩阵的运算
- 问题引入
- 一般方法
- 暴力递推
- 矩阵快速幂
- 对于矩阵快速幂的一些优化
- 特征值与特征向量
- 优化内容
- 总结算法步骤
- 代码
前置知识
矩阵的运算
矩阵加法
只有当两个行数、列数分别相等的矩阵(同型矩阵)相加,加法运算才有意义。
下面举例说明:
\[ \left[\begin{array}{ll} {1} & {2} \\ {3} & {4} \end{array}\right]+\left[\begin{array}{ll} {1} & {3} \\ {2} & {4} \end{array}\right]=\left[\begin{array}{ll} {2} & {5} \\ {5} & {8} \end{array}\right] \]
可以理解为相同的位置加起来,就变成了答案矩阵。
其中,加法满足这些特点:
- 交换律:\(A+B=B+A\)
- 结合律:\((A+B)+C=A+(B+C)\)
矩阵乘法
与数的乘法
所有矩阵中的数全部乘以这个数即为结果,举例:
\[ \left[\begin{array}{ccc} {1} & {-1} & {3} \\ {2} & {6} & {-5} \end{array}\right] \times 4=\left[\begin{array}{ccc} {4} & {-4} & {12} \\ {8} & {24} & {-20} \end{array}\right] \]
满足特性:
- 结合律:\((ab)A=a(bA),(a+b)A=aA+bA\)
- 分配律:\(a(A+B)=aA+aB\)
与矩阵的乘法
先上例子:
\[ \left[\begin{array}{lll} {1} & {2} & {3} \\ {4} & {5} & {6} \end{array}\right] \times\left[\begin{array}{cc} {10} & {5} \\ {8} & {2} \\ {15} & {3} \end{array}\right]=\left[\begin{array}{cc} {71} & {18} \\ {170} & {48} \end{array}\right] \]
即,设 \(A=(a_{ij})_{m\times s},B=(b_{ij})_{s\times n}\) ,那么 \(A\times B=C\) 中,\(C=(c_{ij})_{m\times n}\) 。
必须保证 \(A\) 的列与 \(B\) 的行相同,且不满足交换律。
但满足结合律
向量的特殊说明
这里我们使用的向量是 \(k\) 阶向量,不与一般的向量相同。
即这个向量有 \(k\) 个元素。
问题引入
给出一个数列 \(h\) 若满足满足
\[ h_i=\sum_{j=1}^kh_{i-j}a_j \]
那么我们称其满足 \(k\) 阶齐次线性递推关系。
对于 \(h\) 的前 \(k\) 个元素由题目给定。
现要求 \(h_n\) 的值?
原问题:Shlw loves matrixI
如果 \(\text{BZOJ}\) 炸了,请看下面
BZOJ 太老了...问题与上面一样,
一般方法
对于这样的问题,我们可以有一些比较简单的想法。
暴力递推
如果我们直接使用暴力将式子推到第 \(n\) 项,其时间复杂度大概是 \(\mathcal O(nk)\) 。
矩阵快速幂
对于这样的一个题,我们可以很巧妙地构造初始矩阵:
\[ A= \begin{bmatrix} h_1&h_2&h_3&\ldots&h_{k-1}&h_k \end{bmatrix} \]
而加速矩阵即为
\[ B= \begin{bmatrix} 0&0&\cdots&0&a_1 \\ 1&0&\cdots&0&a_2 \\ 0&1&\cdots&\vdots&a_3 \\ \vdots&\vdots&\ddots&0&\vdots \\ 0&0&0&1&a_k \end{bmatrix} \]
即 \(B\) 的左上——右下对角线往下移一个单位,其上面全部都是 \(1\) ,最后一列顺次从上往下为 \(a_1,a_2,a_3,\ldots a_k\) 。
那么,时间复杂度大概为 \(\mathcal O(k^3\log n)\) ,其中 \(\mathcal O(k^3)\) 为矩阵乘法的时间复杂度,而 \(\mathcal O(\log n)\) 是快速幂的时间复杂度。
对于矩阵快速幂的一些优化
对于我们之前的那些方法,方法一似乎较难优化,因而我们从矩阵快速幂的优化入手,想办法降低其复杂度 \(\mathcal O(k^3\log n)\) 。
特征值与特征向量
我们知道一个矩阵乘一个列向量仍然是一个列向量。
若对于 \(k\) 阶矩阵 \(A\) ,有常数 \(\lambda\) 与列向量 \(\overrightarrow v\) 满足
\[ A\overrightarrow v=\lambda \overrightarrow v \]
那么我们称 \(\lambda\) 为特征值, \(\overrightarrow v\) 为特征向量。
优化内容
由特征值与特征向量的定义,我们可以得到上面的那个等式,即
\[ A\overrightarrow v=\lambda \overrightarrow v \]
令我们的 \(\overrightarrow v\) 为初始矩阵,而 \(A\) 为加速矩阵,而现在我们要找 \(\lambda\) 的值。
考虑将其展开,可得
\[ \begin{bmatrix} a_1&a_2&a_3&\cdots&a_k \\ 0&0&0&\cdots&1 \\ 0&0&\cdots&1&0 \\ &&\vdots \\ 0&1&0&\ldots&0 \end{bmatrix} \times \begin{bmatrix} h_k \\ h_{k-1} \\ h_{k-2} \\ \vdots \\ h_1 \end{bmatrix} = \begin{bmatrix} h_{k+1} \\ h_{k} \\ h_{k} \\ \vdots \\ h_2 \end{bmatrix} =\lambda\overrightarrow v = \begin{bmatrix} \lambda h_{k} \\ \lambda h_{k-1} \\ \lambda h_{k-2} \\ \vdots \\ \lambda h_1 \end{bmatrix} \]
注意到上面的式子中的第三个矩阵和第四个矩阵的等量关系,发现
\[ h_i=\lambda h_{i-1}=\lambda^2h_{i-2}=\lambda^3h_{i-3}=\cdots=\lambda^{i-1}h_1 \]
而根据定义,我们又有
\[ h_{k+1}=a_1h_k+a_2h_{k-1}+\cdots+a_kh_1 \]
将所有的 \(h_i\) 全部换成 \(h_1\) ,可以得到
\[ \lambda^kh_1=a_1\lambda^{k-1}h_1+a_2\lambda^{k-2}h_1+\cdots+a_k\lambda^0h_1 \]
那么,我们可以将 \(h_1\) 全部消掉,得到一个关于 \(\lambda^k\) 的等式,即
\[ \lambda^k=a_1\lambda^{k-1}+a_2\lambda^{k-2}+\cdots +a_k \]
移项,可得
\[ \lambda^k-a_1\lambda^{k-1}-a_2\lambda^{k-2}-\cdots -a_k=0 \]
考虑将 \(\lambda\) 换成关于 \(x\) 的方程,得原式为
\[ x^k-a_1x^{k-1}-a_2x^{k-2}-\cdots-a_k=0 \]
我们令 \(f(x)=x^k-a_1x^{k-1}-a_2x^{k-2}-\cdots-a_k\) 。
下面有关于多项式操作中的除法/取模操作,如果有不会的,请看 多项式的蛇皮操作 。
现在我们求
\[ x^n=f(x)g(x)+r(x) \]
上式中的 \(g(x),r(x)\) ,如果使用多项式的操作,似乎是很好求的,时间复杂度为 \(\mathcal O(k\log k)\) 。
但是,我们求这个东西有什么用呢?
考虑将 \(x\) 替换为矩阵 \(A\) ,那么有 \(f(x)=f(A)=O\) ,其中 \(O\) 为 \(0\) 矩阵。
但是为什么 \(f(A)=O\) ?
这里,Hamilton-Cayley定理说明了这个问题,其内容如下:
对于矩阵 \(A\) 的特征多项式 \(f(x)\) ,满足 \(f(A)=0\) 。
证明看这里(百度百科)
说不定哪天我也会给出自己的证明啊...
如果你还是看不懂,就把它当成一个结论记下来。(作者太菜,只能这样做了)
继续说明,我们有了
\[ A^n=f(A)g(A)+r(A) \]
因为 \(f(A)=O\) ,那么就有
\[ A^n=r(A) \]
而我们通过多项式的操作能够得到 \(r(A)\) 的每一个系数的值。
而如果我们直接这样求的话,其时间复杂度为 \(\mathcal O(k^4)\) 。
但是我们看看我们要求的是 \(A^n\overrightarrow v\) 的每一项,考虑将其等价变换为求 \(r(A)\times \overrightarrow v\)
而由于 \(r(x)\) 的本质是一个多项式,那么它可以写成
\[ r(A)=\sum_{i=0}^{k-1}c_iA^i \]
其中 \(c_i\) 是每一项的系数,那么 \(r(A)\overrightarrow v\) 就可以写为
\[ r(A)\overrightarrow v=\sum_{i=0}^{k-1}c_iA^i\overrightarrow v \]
而 \(A^i\overrightarrow v\)=\(A\times (A^{i-1}\overrightarrow v)\) (满足结合律)
而 \(A^{i-1}\overrightarrow v\) 是一个向量,而向量乘一个矩阵时间复杂度是 \(\mathcal O(k^2)\) ,因为我们要求 \(k\) 个这样的式子,所以时间复杂度为 \(\mathcal O(k^3)\) 。
但是,还可以继续优化。
由于定义,我们知道 \(\overrightarrow v\) 由 \(h_1,h_2,h_3\cdots h_{k-1},h_k\) 组成,且顺序已知。
而 \(A\times \overrightarrow v\) 由 \(h_2,h_3,h_4\cdots h_k,h_{k+1}\) 。
那么,更广泛的推论为
\(A^i\times \overrightarrow v\) 由 \(h_{i+1},h_{i+2}\cdots h_{i+k}\) 组成。
而我们最多只会求到 \(A^{k-1}\overrightarrow v\) ,即我们只会用到前 \(2k\) 个 \(h\) 中的元素。
考虑将其暴力预处理出来,时间复杂度 \(\mathcal O(k^2)\) ,然后我们用 \(\mathcal O(k)\) 的时间即可处理出 \(A^i\times \overrightarrow v\) ,而我们要计算 \(k\) 个这样的多项式,最终时间复杂度为 \(\mathcal O(k^2)\) 。
但是时间复杂度真的是 \(\mathcal O(k^2)\) 吗?
不对的。
问题出在哪里,在于多项式除法的 \(x^n\) 取模 \(f(x)\) 的时候,这个 \(x^n\) 真的可以 \(\mathcal O(1)\) 地表示?
呵呵,此问题中 \(n\le 10^9\) ,存是存不下的。
所以我们只能倍增计算 \(x^n\) ,并且在一边倍增的时候取模 \(f(x)\) 即可,所以此处时间复杂度为 \(\mathcal O(k\log k\log n)\) 。
因而,总的时间复杂度为 \(\mathcal O(max\{k\log k\log n,k^2\})\) 。
总结算法步骤
- 处理出 \(f(x)\) ,时间复杂度 \(\mathcal O(k)\) ;
- 用快速幂求出 \(r(x)\) ,时间复杂度 \(\mathcal O(k\log k\log n)\) ;
- 预处理前 \(2k\) 个 \(h\) ,时间复杂度 \(\mathcal O(k^2)\) ;
- 循环从 \(1\) 到 \(k\) ,每次计算 \(c_iA^i\overrightarrow v\) ,用 \(\mathcal O(k)\) 的时间填 \(A^i\overrightarrow v\) 。总的复杂度 \(\mathcal O(k^2)\) ,此刻得到答案向量;
- 算法结束;
这就是常系数齐次线性递推的主要优化步骤...
代码
例题:Shlw loves matrixI
代码待补...