特征多项式与常系数齐次线性递推
定义
对于 阶矩阵,若存在非零列向量 和数 满足 ,则称 和 为一组对应的特征值和特征向量。
在确定了特征值之后,可以得到对应的无穷多个解。
求解特征值和特征向量
求解特征值和特征向量:
容易发现, 是一个特征值,只需要满足 有解,以 为元容易列出方程,其常数项为均 ,系数矩阵为。
其中是单位矩阵。
这个方程有非零解的充要条件是: (因为如果不为 ,则矩阵满秩,所有向量线性无关,无法得到0向量)
而 是一个 次多项式 ,称为特征多项式,所有的特征值 就是 的根。
应用
加速矩阵乘法:
由 ,迭代该式可以得到 。
特殊矩阵的特征值
上三角矩阵
带入行列式即可知道 。
也就是说,主对角线上所有的 都是 的根。
零化多项式
对于一个矩阵 ,它的一个零化多项式 是满足 的多项式,定义域包含矩阵。
最小多项式:次数最低的零化多项式。
定理
特征多项式:, 定义域不止是 ,还可以是矩阵。
定理指出:矩阵的特征多项式也是它的零化多项式。
即令:
则有:
证明:
将 改写为:
由定理:任意的 阶矩阵都能相似为上三角矩阵 可知,存在可逆矩阵 ,使得:
将 代入 得到:
计算:
即 ,故有
求解特征多项式
带入 个数,求出得 ,得到 个矩阵,通过高斯消元可以 地求出行列式。
然后可 拉格朗日插值求出原来的多项式,总复杂度受限于高斯消元,为 。
求解最小多项式
构造矩阵序列 。
求出它的一个线性递推 ,即:
所以可以由 翻转得到 。
求解 前 项的复杂度受限于矩阵乘法为 ,求解递推式的复杂度为 。
考虑到实际求解递推式时,随机生成了两个向量 。
实际是计算标量序列 的递推式,所以实际每次求出 复杂度应为 。
求这个递推式需要用到 前 项,求解复杂度为 。
因此总复杂度为 。
(但是如果只是求出来并没有什么用,因为求解方法是随机的,甚至连检查一次保证正确都需要 的时间( 为矩阵非 位置个数))
求解稀疏方程组
设方程系数用矩阵 表示,右侧每个方程的常数用向量 表示,答案用向量 表示,则满足关系式
,即 。
求出 线性递推式,反推出 即可。
反推方法:
带入线性递推的 项,则
两边同乘 ,得到
求解矩阵 k 次幂
我们要求解 ,常规做法是直接用快速幂
设矩阵 的一个零化多项式是 ,显然, 可以用一个多项式表示 。
构成了一个 次多项式 。
存在一种合法的表示是 。
所以对于任意实数 , 也合法,也就是相当于我们要求出 对于 这个 多项式取模。
显然可以通过类似快速幂的方式倍增求解这个多项式,每次对 取模复杂度是 ,总时间复杂度 。
最后得到的 是一个 次多项式,带入就可以快速求出 ,可以认为这个复杂度是受限于求解 的 。
对于元矩阵 为稀疏矩阵的情况,设其包含 个非零位置,那么求解 的过程是 的,求解 的过程,是 的。
求解零化多项式的复杂度也是 的,因此总复杂度为 。
而一般的矩阵快速幂是 的,这种方法适用情况非常特殊。
另外,对于并不需要知道整个矩阵的答案,并且 特殊的具体问题,这个方法也十分有效。
求解常系数线性齐次递推
问题是要求数列 。
给出 ,求第 项的值。
线性递推显然可以用初始向量列与转移矩阵的幂次的乘积表示,即,其中为转移矩阵,为初始向量列,我们求的是第项。
对于的情况,我们的转移矩阵。
鉴于它的特殊性,我们可以直接求出它的特征多项式表达式。
由
带入行列式最暴力的求法
枚举一个排列,设排列的逆序对为,
实际上合法的排列只有个,枚举
那么:
当 时,
当 时:
综上,转移矩阵 的特征多项式有简单的表达
假设有 这一项(不需要知道是多少),那么认为初始向量列为 。
这个问题,我们要求的是 的第 项,不需要知道整个矩阵。
类似求出 的过程,求出 。
我们要求解 ,而 已知,求出 后直接带入即可
需要用到多项式取模,求解这个表达式是 的,求完直接带入即可。
模板代码
#4161. Shlw loves matrixI
非 模数, ,可以 卷积,总复杂度 。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
const int md = 1e9 + 7;
inline int pwr(int x, int y) {
int res = 1;
while (y) {
if (y & 1)
res = 1ll * res * x % md;
x = 1ll * x * x % md;
y >>= 1;
}
return res;
}
struct Poly {
vector<int> dp;
Poly(int x = 0) { dp.resize(x); }
inline auto size() { return dp.size(); }
inline int& operator[](int t) { return dp[t]; }
inline Poly Reverse() {
Poly res = *this;
reverse(res.dp.begin(), res.dp.end());
return res;
}
inline Poly operator-(Poly b) {
Poly res(max(dp.size(), b.size()));
for (int i = 0; i < dp.size(); i++) res[i] = (res[i] + dp[i] + md) % md;
for (int i = 0; i < b.size(); i++) res[i] = (res[i] - b[i] + md) % md;
return res;
}
inline Poly operator*(int x) {
Poly res = *this;
for (auto& it : res.dp) it = 1ll * it * x % md;
return res;
}
inline Poly operator*(Poly y) {
Poly res(dp.size() + y.size() - 1);
for (int i = 0; i < dp.size(); i++) {
for (int j = 0; j < y.size(); j++) res[i + j] = (res[i + j] + 1ll * dp[i] * y[j]) % md;
}
return res;
}
inline Poly Inv(int len) {
if (len == 1) {
Poly res(1);
res[0] = pwr(dp[0], md - 2);
return res;
}
Poly res = this->Inv((len + 1) / 2), tmp = *this;
tmp.dp.resize(len);
res = res * 2 - tmp * res * res;
res.dp.resize(len);
return res;
}
inline Poly operator/(Poly b) {
int N = dp.size(), M = b.size();
if (N < M)
return Poly();
Poly res = this->Reverse() * (b.Reverse().Inv(N - M + 1));
res.dp.resize(N - M + 1);
return res.Reverse();
}
inline Poly operator%(Poly b) {
Poly G = *this / b, res = *this - (G * b);
res.dp.resize(min(dp.size(), b.size()));
return res;
}
};
inline Poly Polypower(Poly x, int y, Poly MD) {
Poly res(1);
res[0] = 1;
while (y) {
if (y & 1)
res = res * x % MD;
x = x * x % MD;
y >>= 1;
}
return res;
}
int a[40005], k;
int main() {
scanf("%d%d", &k, &n);
Poly F(n + 1), X(2);
F[n] = 1;
X[1] = 1;
for (int i = n - 1; ~i; i--) scanf("%d", &F[i]), F[i] = (md - F[i]) % md;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
Poly tmp = Polypower(X, k, F);
int res = 0;
for (int i = 0; i < n; i++) res = (res + 1ll * a[i] * tmp[i]) % md;
printf("%d\n", (res + md) % md);
return 0;
}
P4723 【模板】常系数齐次线性递推
模数, , 卷积,总复杂度 。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
const int md = 998244353, G = 3, Gi = (md + 1) / 3;
int r[1 << 20];
inline int pwr(int x, int y) {
int res = 1;
while (y) {
if (y & 1)
res = 1ll * res * x % md;
x = 1ll * x * x % md;
y >>= 1;
}
return res;
}
inline void NTT(vector<int> &dp, int lim, int W) {
for (int i = 0; i < (1 << lim); i++)
if (i < r[i])
swap(dp[i], dp[r[i]]);
for (int i = 0; i < lim; i++) {
int w = pwr(W, (md - 1) / (1 << (i + 1)));
for (int j = 0; j < (1 << lim); j += (1 << (i + 1))) {
int Pw = 1;
for (int t = 0; t < (1 << i); t++) {
int x = dp[j + t], y = 1ll * Pw * dp[j + (1 << i) + t] % md;
dp[j + t] = (x + y) % md;
dp[j + (1 << i) + t] = (x - y + md) % md;
Pw = 1ll * Pw * w % md;
}
}
}
}
struct Poly {
vector<int> dp;
Poly(int x = 0) { dp.resize(x); }
inline auto size() { return dp.size(); }
inline int &operator[](int t) { return dp[t]; }
inline Poly Reverse() {
Poly res = *this;
reverse(res.dp.begin(), res.dp.end());
return res;
}
inline Poly operator-(Poly b) {
Poly res(max(dp.size(), b.size()));
for (int i = 0; i < dp.size(); i++) res[i] = (res[i] + dp[i] + md) % md;
for (int i = 0; i < b.size(); i++) res[i] = (res[i] - b[i] + md) % md;
return res;
}
inline Poly operator*(int x) {
Poly res = *this;
for (auto &it : res.dp) it = 1ll * it * x % md;
return res;
}
inline Poly operator*(Poly y) {
Poly x = *this, res(dp.size() + y.size() - 1);
int lim = 0;
while ((1 << lim) <= res.size()) lim++;
for (int i = 0; i < (1 << lim); i++) r[i] = (r[i >> 1] >> 1) + ((i & 1) << (lim - 1));
vector<int> a(1 << lim), b(1 << lim), c(1 << lim);
for (int i = 0; i < dp.size(); i++) a[i] = dp[i];
for (int i = 0; i < y.size(); i++) b[i] = y[i];
NTT(a, lim, G);
NTT(b, lim, G);
for (int i = 0; i < (1 << lim); i++) c[i] = 1ll * a[i] * b[i] % md;
NTT(c, lim, Gi);
int inv = pwr(1 << lim, md - 2);
for (int i = 0; i < res.size(); i++) res[i] = 1ll * c[i] * inv % md;
return res;
}
inline Poly Inv(int len) {
if (len == 1) {
Poly res(1);
res[0] = pwr(dp[0], md - 2);
return res;
}
Poly res = this->Inv((len + 1) / 2), tmp = *this;
tmp.dp.resize(len);
res = res * 2 - tmp * res * res;
res.dp.resize(len);
return res;
}
inline Poly operator/(Poly b) {
int N = dp.size(), M = b.size();
if (N < M)
return Poly();
Poly res = this->Reverse() * (b.Reverse().Inv(N - M + 1));
res.dp.resize(N - M + 1);
return res.Reverse();
}
inline Poly operator%(Poly b) {
Poly G = *this / b, res = *this - (G * b);
res.dp.resize(min(dp.size(), b.size()));
return res;
}
};
inline Poly Polypower(Poly x, int y, Poly MD) {
Poly res(1);
res[0] = 1;
while (y) {
if (y & 1)
res = res * x % MD;
x = x * x % MD;
y >>= 1;
}
return res;
}
int a[40005], k;
int main() {
scanf("%d%d", &k, &n);
Poly F(n + 1), X(2);
F[n] = 1;
X[1] = 1;
for (int i = n - 1; ~i; i--) scanf("%d", &F[i]), F[i] = (md - F[i]) % md;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
Poly tmp = Polypower(X, k, F);
int res = 0;
for (int i = 0; i < n; i++) res = (res + 1ll * a[i] * tmp[i]) % md;
printf("%d\n", (res + md) % md);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】