矩阵相关

矩阵相关运算

结构体定义

将矩阵运算定义成结构体,并重载相应的运算符,可以简化后续的运算。

typedef long long ll;
const int N = 110;
int n, mod;

struct Mat {
    int n, m; //矩阵的行和列
    int a[N][N];
    void zero() { //0矩阵 
        memset(a, 0, sizeof(a));
    }
    void one() { //n*n的单位矩阵 
        zero();
        for (int i = 1; i <= n; i++) a[i][i] = 1;
    }
    void resize(int x, int y) { //设置矩阵大小 
        n = x; m = y;
    }
    //矩阵加,第二个const表示不能修改成员变量的值,对数据起到保护作用,下同
    Mat operator +(const Mat &A) const {
        Mat res;
        res.resize(n, m);

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                res.a[i][j] = (a[i][j] + A.a[i][j]) % mod; 
            }
        }
        return res;
    }

    //矩阵减
    Mat operator -(const Mat &A) const {
        Mat res;
        res.resize(n, m);

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                res.a[i][j] = (a[i][j] - A.a[i][j]) % mod; 
            }
        }
        return res;
    }

    //矩阵乘
    Mat operator *(const Mat &A) const {
        Mat res;
        res.resize(n, A.m);
        res.zero();

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= A.m; j++) {
                for (int k = 1; k <= m; k++) {
                    res.a[i][j] = ((ll)a[i][k] * A.a[k][j] + res.a[i][j]) % mod;
                }
            }
        }
        return res;
    }
    //输出矩阵
    void ouput() {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                printf("%d ", a[i][j]);
            }
            putchar('\n');
        }
    }
};

矩阵快速幂

可以利用上面定义好的结构,方便写出矩阵快速幂的形式。

例 1:计算斐波那契数

【问题描述】

计算斐波那契数列的第 \(n\)\(F_n\)\(1\le n\le 2\times 10^9\))。

【分析】

根据斐波那契数列的递推式 \(F_n = F_{n-1} + F_{n-2}\),我们可以构建一个 \(2\times 2\) 的矩阵来表示从 \(F_n,F_{n+1}\)\(F_{n+1},F_{n+2}\) 的变换。于是在计算这个矩阵的 \(n\) 次幂的时侯,我们使用快速幂的思想,可以在 \(\Theta(\log n)\) 的时间内计算出结果。

根据斐波那契数列 递推公式的矩阵形式:

\[\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \cdot \begin{bmatrix} F_{n-1} \\ F_{n-2} \end{bmatrix} = \begin{bmatrix} F_n \\ F_{n-1} \end{bmatrix} \]

我们定义初始矩阵

\[\begin{bmatrix}F_2 \\F_1\end{bmatrix} = \begin{bmatrix}1 \\1\end{bmatrix}, \text{base} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \]

那么,

\[\text{ans}=\text{base}^{n-2}\cdot\begin{bmatrix}F_2 \\F_1\end{bmatrix} \]

\(F_n\) 就等于 \(ans\) 矩阵的第一行第一列元素。

为什么要乘上 \(\text{base}\) 矩阵的 \(n-2\) 次方而不是 \(n\) 次方呢?因为 \(F_1, F_2\) 是不需要进行矩阵乘法就能求的。也就是说,如果只进行一次乘法,就已经求出 \(F_3\) 了,手算一下就能理解了。

【示例代码】

//矩阵快速幂
Mat qpow(Mat A, int b) {
    Mat res;
    res.resize(2, 2);
    res.one();
    while (b) {
        if (b & 1) {
            res = res * A;
        }
        A = A * A;
        b >>= 1;
    }
    return res;
}

int main() {
    Mat mat;
    scanf("%d%d", &n, &mod);
    //初始化矩阵
    mat.resize(2, 2);
    mat.a[1][1] = mat.a[1][2] = mat.a[2][1] = 1;
    mat.a[2][2] = 0;

    mat = qpow(mat, n-2);
    int ans = (mat.a[1][1] + mat.a[2][1]) % mod;
    printf("%d\n", ans);
    return 0;
}

例 2:计算递推式

【问题描述】

给定递推式 \(F_n=F_{n-1}+F_{n-3}\),计算数列的第 \(n\)\(F_n\)\(1\le n\le 2\times 10^9\))。

【分析】

按照上题的思路,我们看到 \(F_n\) 只与前面两项有关,因此直接可以想到一个写法:

\[A\cdot \begin{bmatrix} F_{n-1}\\ F_{n-3}\end{bmatrix} = \begin{bmatrix} F_{n}\\ F_{n-2}\end{bmatrix} \]

但是此时发现,后面还有一项 \(F_{n-2}\) 无法直接通过 \(F_{n-1}\)\(F_{n-3}\) 得到,且两项之间隔一项 \(F_{n-2}\),因此我们对等号前面的列向量再加一项 \(F_{n-2}\),后面加一项 \(F_{n-2}\),可以使整个式子变得完整,得到:

\[A\cdot \begin{bmatrix} F_{n-1}\\ F_{n-2}\\ F_{n-3}\end{bmatrix} = \begin{bmatrix} F_{n}\\ F_{n-1}\\ F_{n-2}\end{bmatrix} \]

由递推式可得:

\[\begin{cases} 1\times F_{n-1}+0\times F_{n-2}+1\times F_{n-3}=F_n\\ 1\times F_{n-1}+0\times F_{n-2}+0\times F_{n-2}=F_{n-1}\\ 0\times F_{n-1}+1\times F_{n-2}+0\times F_{n-2}=F_{n-2}\\ \end{cases} \]

我们把对应的系数写成矩阵,则:

\[A=\begin{bmatrix} 1 &0 &1\\ 1 &0 &0\\ 0 &1 &0 \end{bmatrix} \]

剩下的就是痛快地跑矩阵快速幂了。

例 3:计算递推式(加强版)

【问题描述】

给定递推式 \(F_n=aF_{n-1}+bF_{n-2}+c\times n+d*e^n\),计算数列的第 \(n\)\(F_n\)\(1\le n\le 2\times 10^9\)),其中 \(a,b,c,d,e\) 为常数。

【分析】

\(F_n\)\(F_{n-1},F_{n-2},n\) 有关,考虑构造一个矩阵描述状态。

但是如果矩阵仅有这三个元素 \(\begin{bmatrix}F_n\\ F_{n-1}\\ n\end{bmatrix}\) 是难以构造出转移方程的,因为 \(n\) 变换成 \(n+1\) 需要加 \(1\),而 \(e^n\) 又是幂的形式,这两种无法用前面的三项描述出来。因此考虑构造一个更大的矩阵。

我们可以把每个与 \(n\) 有关的项看成一个独立的元素,并且由 \(n\) 变换成 \(n+1\) 还要加 \(1\),所以我们需要加再加一个元素 \(1\)。此时我们希望的是由 \(\begin{bmatrix}F_{n-1}\\ F_{n-2}\\ n\\ e^n\\ 1\end{bmatrix}\) 变换成 \(\begin{bmatrix}F_{n}\\ F_{n-1}\\ n+1\\ e^{n+1}\\ 1\end{bmatrix}\)

\[\begin{cases} a\times F_{n-1}+b\times F_{n-2}+c\times n+d\times e^n+0\times 1=F_n\\ 1\times F_{n-1}+0\times F_{n-2}+0\times n+0\times e^n+0\times 1=F_{n-1}\\ 0\times F_{n-1}+0\times F_{n-2}+1\times n+0\times e^n+1\times 1=n+1\\ 0\times F_{n-1}+0\times F_{n-2}+0\times n+e\times e^n+0\times 1=e^{n+1}\\ 0\times F_{n-1}+0\times F_{n-2}+0\times n+0\times e^n+1\times 1=1\\ \end{cases} \]

因此我们得到变换矩阵为

\[A=\begin{bmatrix} a &b &c &d &0\\ 1 &0 &0 &0 &0\\ 0 &0 &1 &0 &1\\ 0 &0 &0 &e &0\\ 0 &0 &0 &0 &1\\ \end{bmatrix} \]

剩下的又是痛快地跑矩阵快速幂了。

posted @ 2024-01-26 09:26  狂飙霹雳虎  阅读(15)  评论(0编辑  收藏  举报