特征多项式与常系数齐次线性递推

定义

对于 n 阶矩阵,若存在非零列向量 x 和数 λ 满足 Ax=λx,则称 λx 为一组对应的特征值和特征向量。

在确定了特征值之后,可以得到对应的无穷多个解。

求解特征值和特征向量

求解特征值和特征向量:

容易发现,λ 是一个特征值,只需要满足 Ax=λx 有解,以 x 为元容易列出方程,其常数项为均 0,系数矩阵为。

(λA1,1A1,2A1,3A1,nA2,1λA2,2A2,3A2,nA3,1A3,2λA3,3A3,nAn,1An,2An,3λAn,n)=λIA

其中I是单位矩阵。

这个方程有非零解的充要条件是:det(λIA)=0 (因为如果不为 0,则矩阵满秩,所有向量线性无关,无法得到0向量)

det(λIA) 是一个 n 次多项式 p(λ),称为特征多项式,所有的特征值 λ 就是 p(λ) 的根。

应用

加速矩阵乘法:

Ax=λx,迭代该式可以得到 Anx=λnx

特殊矩阵的特征值

上三角矩阵

λIA=(λA1,1A1,2A1,3A1,n0λA2,2A2,3A2,n00λA3,3A3,n000λAn,n)

带入行列式即可知道 det(λIA)=(λAi,i)

也就是说,主对角线上所有的 Ai,i 都是 det(λIA)=0 的根。

零化多项式

对于一个矩阵 A,它的一个零化多项式 φ(λ) 是满足 φ(A)=0 的多项式,定义域包含矩阵。

最小多项式:次数最低的零化多项式。

Cayley-Hamilton 定理

特征多项式:p(λ)=|λIA|λ 定义域不止是 R,还可以是矩阵。

Cayley-Hamilton 定理指出:矩阵的特征多项式也是它的零化多项式。

即令:

φ(λ)=det(λIA)=λn+a1λn1++an1λ+an

则有:

φ(A)=An+a1An1++an1A+an=O

证明:

φ(λ) 改写为:

φ(λ)=(λλ1)(λλ2)(λλn)

由定理:任意的 n 阶矩阵都能相似为上三角矩阵 可知,存在可逆矩阵 P,使得:

PAP1=(λ1λ2λn)

PAP1 代入 φ(λ) 得到:

φ(PAP1)=(PAP1λ1I)(PAP1λ2I)(PAP1λnI)

计算:

(0λ2λ1λnλ1)×(λ1λ20λnλ2)×(λ1λnλ2λn0)=O

φ(PAP1)=Pφ(A)P1=O,故有 φ(A)=O

求解特征多项式

带入 n 个数,求出得 det(xInA),得到 n 个矩阵,通过高斯消元可以 O(n3) 地求出行列式。

然后可 O(n2) 拉格朗日插值求出原来的多项式,总复杂度受限于高斯消元,为 O(n4)

求解最小多项式

构造矩阵序列 ai=Ai

求出它的一个线性递推 ri,即:

j=0mrjaij=j=0mrjAij=(j=0mrmjAj)Aim=0j=0mrmjAj=0

所以可以由 ri 翻转得到 f(λ)

求解 ain 项的复杂度受限于矩阵乘法为 O(n4),求解递推式的复杂度为 O(n3)

考虑到实际求解递推式时,随机生成了两个向量 u,v

实际是计算标量序列 {uAiv} 的递推式,所以实际每次求出 uAi 复杂度应为 O(n2)

求这个递推式需要用到 ai2n 项,求解复杂度为 O(n3)

因此总复杂度为 O(n3)

(但是如果只是求出来并没有什么用,因为求解方法是随机的,甚至连检查一次保证正确都需要 O(n2(n+e)) 的时间(e 为矩阵非 0 位置个数))

求解稀疏方程组

设方程系数用矩阵 A 表示,右侧每个方程的常数用向量 b 表示,答案用向量 x 表示,则满足关系式

Ax=b,即 x=A1b

求出 {Aib} 线性递推式,反推出 A1b 即可。

反推方法:

带入线性递推的 m 项,则 i=0mAmibri=0

两边同乘 A1,得到 A1brm+i=0m1Amibri=0

求解矩阵 k 次幂

我们要求解 Ak,常规做法是直接用快速幂

设矩阵 A 的一个零化多项式是 f(λ) ,显然,Ak 可以用一个多项式表示 Ak=0kwiAi

{wi} 构成了一个 k+1 次多项式 Fk(x)

存在一种合法的表示是 Fk(x)=xk

f(A)=0i,f(A)Ai=0

所以对于任意实数 TGk(x)=xkTf(x) 也合法,也就是相当于我们要求出 xk 对于 f(x) 这个 n+1 多项式取模。

显然可以通过类似快速幂的方式倍增求解这个多项式,每次对 f(x) 取模复杂度是 O(nlogn) ,总时间复杂度 O(nlogmlogn)

最后得到的 F(x) 是一个 n 次多项式,带入就可以快速求出 Ak,可以认为这个复杂度是受限于求解 A0,A1,,An1O(n4)

对于元矩阵 A 为稀疏矩阵的情况,设其包含 e 个非零位置,那么求解 BA 的过程是 O(ne) 的,求解 A0,A1,,An1 的过程,是 O(n2e) 的。

求解零化多项式的复杂度也是 O(n2(n+e)) 的,因此总复杂度为 O(n2(n+e))

而一般的矩阵快速幂是 O(n3logk) 的,这种方法适用情况非常特殊。

另外,对于并不需要知道整个矩阵的答案,并且 A0,A1,,An1 特殊的具体问题,这个方法也十分有效。

求解常系数线性齐次递推

问题是要求数列 fi=j=1najfij

给出 f0,f1,,fn1,求第 k 项的值。

线性递推显然可以用初始向量列与转移矩阵的幂次的乘积表示,即fi=(SAi)n,其中A为转移矩阵,S为初始向量列,我们求的是第n项。

对于n=4的情况,我们的转移矩阵。

A=(a41a31a21a1)

鉴于它的特殊性,我们可以直接求出它的特征多项式表达式。

λInA=(λa41λa31λa21λa1)

带入行列式最暴力的求法

枚举一个排列pi,设排列p的逆序对为f(p)|A|=(1)f(p)ΠAi,pi

实际上合法的排列只有n个,枚举 pi=n

那么:

pj={jj<inj=ij1j>i

i=n 时,(1)f(p)Ai,pi=λna1λn1

in 时:

{f(p)=niAi,pi=(1)ni+1λiani+1

 (1)f(p)Ai,pi=λiani+1

综上,转移矩阵 A 的特征多项式有简单的表达

p(λ)=|λInA|=λna1λn1a2λn2an

假设有 f0 这一项(不需要知道是多少),那么认为初始向量列为 S=(f(n1),f(n2),,f0)

这个问题,我们要求的是 SAk 的第 n 项,不需要知道整个矩阵。

类似求出 Ak 的过程,求出 Fk(x)modp(λ)

我们要求解 (SAk)n=1n[xi]F(x)(SAi)n,而 (SAi)n=fi 已知,求出 F(x) 后直接带入即可

需要用到多项式取模,求解这个表达式是 O(nlognlogk) 的,求完直接带入即可。

模板代码

#4161. Shlw loves matrixI

NTT 模数,k2000 ,可以 O(k2) 卷积,总复杂度 O(k2logn)

点击查看代码
#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 【模板】常系数齐次线性递推

NTT 模数,k32000O(klogk) 卷积,总复杂度 O(klogklogn)

点击查看代码

#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;
}

posted @   一粒夸克  阅读(550)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示

目录导航