「学习笔记」常系数齐次线性递推
前置知识
矩阵的运算
矩阵加法
只有当两个行数、列数分别相等的矩阵(同型矩阵)相加,加法运算才有意义。
下面举例说明:
可以理解为相同的位置加起来,就变成了答案矩阵。
其中,加法满足这些特点:
- 交换律:\(A+B=B+A\)
- 结合律:\((A+B)+C=A+(B+C)\)
矩阵乘法
与数的乘法
所有矩阵中的数全部乘以这个数即为结果,举例:
满足特性:
- 结合律:\((ab)A=a(bA),(a+b)A=aA+bA\)
- 分配律:\(a(A+B)=aA+aB\)
与矩阵的乘法
先上例子:
即,设 \(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\) 的值?
如果 \(\text{BZOJ}\) 炸了,请看下面
BZOJ 太老了...问题与上面一样,
一般方法
对于这样的问题,我们可以有一些比较简单的想法。
暴力递推
如果我们直接使用暴力将式子推到第 \(n\) 项,其时间复杂度大概是 \(\mathcal O(nk)\) 。
矩阵快速幂
对于这样的一个题,我们可以很巧妙地构造初始矩阵:
而加速矩阵即为
即 \(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\) 满足
那么我们称 \(\lambda\) 为特征值, \(\overrightarrow v\) 为特征向量。
优化内容
由特征值与特征向量的定义,我们可以得到上面的那个等式,即
令我们的 \(\overrightarrow v\) 为初始矩阵,而 \(A\) 为加速矩阵,而现在我们要找 \(\lambda\) 的值。
考虑将其展开,可得
注意到上面的式子中的第三个矩阵和第四个矩阵的等量关系,发现
而根据定义,我们又有
将所有的 \(h_i\) 全部换成 \(h_1\) ,可以得到
那么,我们可以将 \(h_1\) 全部消掉,得到一个关于 \(\lambda^k\) 的等式,即
移项,可得
考虑将 \(\lambda\) 换成关于 \(x\) 的方程,得原式为
我们令 \(f(x)=x^k-a_1x^{k-1}-a_2x^{k-2}-\cdots-a_k\) 。
下面有关于多项式操作中的除法/取模操作,如果有不会的,请看 多项式的蛇皮操作 。
现在我们求
上式中的 \(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\) 。
说不定哪天我也会给出自己的证明啊...
如果你还是看不懂,就把它当成一个结论记下来。(作者太菜,只能这样做了)
继续说明,我们有了
因为 \(f(A)=O\) ,那么就有
而我们通过多项式的操作能够得到 \(r(A)\) 的每一个系数的值。
而如果我们直接这样求的话,其时间复杂度为 \(\mathcal O(k^4)\) 。
但是我们看看我们要求的是 \(A^n\overrightarrow v\) 的每一项,考虑将其等价变换为求 \(r(A)\times \overrightarrow v\)
而由于 \(r(x)\) 的本质是一个多项式,那么它可以写成
其中 \(c_i\) 是每一项的系数,那么 \(r(A)\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)\) ,此刻得到答案向量;
- 算法结束;
这就是常系数齐次线性递推的主要优化步骤...
代码
代码待补...