OI线性代数——矩阵

OI线性代数——矩阵

本章只讨论OI中的线性代数,过于高深的线性代数的知识请参考其他教材。

矩阵与向量

矩阵是一个数学概念,对应着计算机中的二维数组。例如:

A = [ 1 3 5 1 2 4 ] A= \begin{bmatrix} 1 & 3 & 5 \\ 1 & 2 & 4 \end{bmatrix} A=[113254]

是一个 2 × 3 2 \times 3 2×3矩阵,由两行三列组成,通过 A [ i , j ] A[i,j] A[i,j]来指定矩阵中第 i i i行第 j j j列的元素,例如 A [ 1 , 3 ] = 3 A[1,3]=3 A[1,3]=3

有一类特殊的 n × 1 n \times 1 n×1矩阵叫做向量,例如:

V = [ 1 2 3 ] V = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} V=123

是一个拥有 3 3 3个元素的矩阵,此篇文章不再介绍计算几何中的向量。

方阵是一种 n × n n \times n n×n的矩阵,例如:

B = [ 1 3 1 2 ] B = \begin{bmatrix} 1 & 3 \\ 1 & 2 \end{bmatrix} B=[1132]

是一个 2 × 2 2 \times 2 2×2的方阵,一般我们研究方阵的性质。

矩阵的运算

矩阵基本运算

  • 矩阵加法:定义 A + B A + B A+B,为两个相同大小的矩阵对应元素分别相加。
  • 矩阵减法:定义 A − B A - B AB,为两个相同大小的矩阵对应元素分别相减。
  • 矩阵数乘:定义 a A aA aA,为矩阵 A A A的每一个元素都乘以 a a a
  • 矩阵转置:定义 A T A^T AT,将每一个 A [ i , j ] A[i,j] A[i,j] A [ j , i ] A[j,i] A[j,i]互换位置。

矩阵乘法

定义 A B AB AB,为 A A A矩阵 a × n a \times n a×n乘以矩阵 B B B矩阵 n × b n \times b n×b,结果矩阵为大小为 a × b a \times b a×b

完整定义:

A B [ i , j ] = ∑ k = 1 n A [ i , k ] × B [ k , j ] AB[i,j] = \sum_{k=1}^n A[i,k] \times B[k,j] AB[i,j]=k=1nA[i,k]×B[k,j]

矩阵乘法满足结合律,但不满足交换律。

定义单位矩阵为一个 n × n n \times n n×n的方阵,其对角元素均为 1 1 1,其他元素均为 0 0 0。一个矩阵乘以单位矩阵不会改变这个矩阵。

矩阵的幂

定义矩阵的幂为 A k = A ⋅ A … A A^k=A \cdot A \ldots A Ak=AAA,即 k k k A A A矩阵相乘。

矩阵相乘的朴素算法需要 O ( k n 3 ) O(kn^3) O(kn3)时间,经过矩阵快速幂优化之后可以达到 O ( n 3 log ⁡ k ) O(n^3 \log k) O(n3logk)的时间。

同理其他快速幂,我们也采用二进制快速幂的方式进行优化。

P3390 矩阵快速幂

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

int n;
ll k;

ll mod = 1000000007;

struct Matrix
{
    ll arr[105][105];

    Matrix()
    {
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                arr[i][j] = 0;
            }
        }
    }

    Matrix operator*(Matrix &o)
    {
        Matrix ans;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                for (int k = 1; k <= n; k++)
                {
                    ans.arr[i][j] = (ans.arr[i][j] + (arr[i][k] * o.arr[k][j]) % mod) % mod;
                }
            }
        }
        return ans;
    }

    Matrix fpow(ll e)
    {
        Matrix temp = *this;
        Matrix ans;

        for (int i = 1; i <= n; i++)
            ans.arr[i][i] = 1;

        for (; e; e >>= 1, temp = temp * temp)
        {
            if (e & 1)
                ans = ans * temp;
        }

        return ans;
    }
};

int main()
{
    scanf("%d %lld", &n, &k);

    Matrix m;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            scanf("%lld", &m.arr[i][j]);
        }
    }

    m = m.fpow(k);

    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            printf("%lld ", m.arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

线性递推

一个线性递推方程是一种数列,具有一些初始值 f ( 0 ) , f ( 1 ) , … , f ( k − 1 ) f(0),f(1),\ldots,f(k-1) f(0),f(1),,f(k1)和一个递推式:

f ( n ) = c 1 f ( n − 1 ) + c 2 f ( n − 2 ) + … + c k f ( n − k ) f(n) = c_1f(n-1) + c_2f(n-2) + \ldots + c_kf(n-k) f(n)=c1f(n1)+c2f(n2)++ckf(nk)

这里 c i c_i ci都是常数。普通的递推法的时间复杂度是 O ( k n ) O(kn) O(kn),而使用矩阵快速幂递推法的时间是 O ( k 3 log ⁡ n ) O(k^3 \log n) O(k3logn)

斐波那契数列递推

斐波那契数列有如下递推式:

f ( 0 ) = 0 f ( 1 ) = 1 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(0) = 0 \\ f(1) = 1 \\ f(n) = f(n-1) + f(n-2) f(0)=0f(1)=1f(n)=f(n1)+f(n2)

我们定义数列向量为:

V k = [ f ( k ) f ( k − 1 ) ] V_k = \begin{bmatrix} f(k) \\ f(k-1) \end{bmatrix} Vk=[f(k)f(k1)]

定义递推矩阵为:

V k + 1 = T V k V_{k+1} = TV_k Vk+1=TVk

使用矩阵进行线性递推的过程需要构造出 T T T矩阵。通过合理构造,我们有:

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

故有:

V n = T n [ 1 0 ] V_n = T^n \begin{bmatrix} 1 \\ 0 \end{bmatrix} Vn=Tn[10]

T n T^n Tn可以在对数时间内计算出来。

更一般的递推

类比于上述定义,对于式子:

f ( n ) = c 1 f ( n − 1 ) + c 2 f ( n − 2 ) + … + c k f ( n − k ) f(n) = c_1f(n-1) + c_2f(n-2) + \ldots + c_kf(n-k) f(n)=c1f(n1)+c2f(n2)++ckf(nk)

我们可以构造出更一般的 T T T矩阵为:

T = [ c 1 c 2 ⋯ c k 1 ⋯ 0 0 ⋮ ⋱ ⋮ ⋮ 0 ⋯ 1 0 ] T = \begin{bmatrix} c_1 & c_2 & \cdots & c_k \\ 1 & \cdots & 0 & 0 \\ \vdots & \ddots & \vdots & \vdots \\ 0 & \cdots & 1 & 0 \\ \end{bmatrix} T=c110c201ck00

P1939

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

int n;

ll mod = 1000000007;

struct Matrix
{
    ll arr[4][4];

    Matrix()
    {
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                arr[i][j] = 0;
            }
        }
    }

    Matrix operator*(const Matrix &o)
    {
        Matrix ans;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                for (int k = 1; k <= n; k++)
                {
                    ans.arr[i][j] = (ans.arr[i][j] + (arr[i][k] * o.arr[k][j]) % mod) % mod;
                }
            }
        }
        return ans;
    }

    Matrix fpow(ll e)
    {
        Matrix temp = *this;
        Matrix ans;

        for (int i = 1; i <= n; i++)
            ans.arr[i][i] = 1;

        for (; e; e >>= 1, temp = temp * temp)
        {
            if (e & 1)
                ans = ans * temp;
        }

        return ans;
    }
};

struct Query
{
    ll k;
    ll id;

    bool operator<(const Query &o) const
    {
        return k < o.k;
    }
} ques[120];

ll ANS[120];

int main()
{
    n = 3;
    int T;
    scanf("%d", &T);

    for (int i = 0; i < T; i++)
    {
        scanf("%lld", &ques[i].k);
        ques[i].id = i;
    }

    sort(ques, ques + T);
    Matrix curr;
    ll currE = 0;
    curr.arr[1][1] = 1;
    curr.arr[2][2] = 1;
    curr.arr[3][3] = 1;
    for (int i = 0; i < T; i++)
    {
        ll k = ques[i].k;
        if (k <= 3)
        {
            ANS[ques[i].id] = 1;
            continue;
        }

        Matrix m;
        m.arr[1][1] = 1;
        m.arr[1][3] = 1;
        m.arr[2][1] = 1;
        m.arr[3][2] = 1;

        curr = curr * m.fpow(k - 3 - currE);
        currE = k - 3;

        ANS[ques[i].id] = (curr.arr[1][1] + (curr.arr[1][2] + curr.arr[1][3]) % mod) % mod;
    }

    for (int i = 0; i < T; i++)
    {
        printf("%lld\n", ANS[i]);
    }

    return 0;
}

高斯消元与线性方程、行列式与矩阵的逆

行列式与矩阵的逆

首先我们定义矩阵伴随因子也称余子式我为:

C [ i , j ] = ( − 1 ) i + j det ⁡ ( M [ i , j ] ) C[i,j] = (-1)^{i+j}\det(M[i,j]) C[i,j]=(1)i+jdet(M[i,j])

其中 M [ i , j ] M[i,j] M[i,j]是原矩阵 A A A去掉 i i i行和 j j j列后剩下的矩阵。

则行列式的定义(按照第一行展开)为:

det ⁡ ( A ) = ∑ j = 1 n A [ 1 , j ] C [ 1 , j ] \det(A) = \sum_{j = 1}^n A[1,j] C[1,j] det(A)=j=1nA[1,j]C[1,j]

还可以按照某一行某一列展开均可。

我们定义矩阵 A A A的逆矩阵为:

A A − 1 = I AA^{-1} = I AA1=I

并且

A − 1 [ i , j ] = C [ j , i ] det ⁡ A A^{-1}[i,j] = \frac{C[j,i]}{\det A} A1[i,j]=detAC[j,i]

一个方阵有逆矩阵当且仅当这个矩阵 det ⁡ A ≠ 0 \det A \neq 0 detA=0

P4783

高斯消元与线性方程组

每一个线性方程组都可以写成是一个矩阵方程 A x ⃗ = b ⃗ A\vec{x}=\vec{b} Ax =b ,其中 A A A称为系数矩阵, x ⃗ \vec{x} x 为解向量, b ⃗ \vec{b} b 为常数向量。

高斯消元法理论的核心主要如下:

  • 两方程互换,解不变;
  • 方程乘以非零数 k k k ,解不变;
  • 方程乘以数 k k k加上另一方程,解不变。

高斯消元法在将增广矩阵化为最简形后对于自由未知量的赋值,需要掌握线性相关知识,且赋值存在人工经验的因素,使得在学习过程中有一定的困难,将高斯消元法划分为五步骤,从而提出五步骤法,内容如下:

  1. 增广矩阵行初等行变换为行最简形;
  2. 还原线性方程组;
  3. 求解第一个变量;
  4. 补充自由未知量;
  5. 列表示方程组通解。

P3389 弱-高斯消元
P2455 强-高斯消元

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

typedef long long ll;

double matrix[105][105];
double esp = 1e-3;

int main()
{
    int n;
    scanf("%d", &n);

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j <= n; j++)
        {
            scanf("%lf", &matrix[i][j]);
        }
    }

    for (int r = 0; r < n; r++)
    {
        int domain = r;

        // 选择系数最大的那行作为主元,为了减少精度损失
        for (int j = r; j < n; j++)
        {
            if (abs(matrix[j][r]) > abs(matrix[domain][r]))
                domain = j;
        }

        // 没有主元,跳过
        if (abs(matrix[domain][r]) < esp)
        {
            continue;
        }

        // 交换两行

        for (int j = 0; j <= n; j++)
        {
            swap(matrix[domain][j], matrix[r][j]);
        }

        // 消元

        for (int l = 0; l < n; l++)
        {
            if (l != r)
                for (int j = n; j >= r; j--)
                {
                    matrix[l][j] -= matrix[r][j] / matrix[r][r] * matrix[l][r];
                }
        }
    }

    // 检查解的存在性

    for (int r = 0; r < n; r++)
    {
        // 寻找这一行的主元
        int ptr = r;
        while (abs(matrix[r][ptr]) < esp && ptr <= n)
        {
            ptr++;
        }
        // 如果主元在常数列上,那么无解
        if (ptr == n)
        {
            printf("-1");
            return 0;
        }
    }

    // 如果存在缺少主元的情况,那么有无穷多解
    for (int r = 0; r < n; r++)
    {
        if (abs(matrix[r][r]) < esp)
        {
            printf("0");
            return 0;
        }
    }

    // 否则有唯一解
    for (int r = 0; r < n; r++)
    {
        // 除以主元
        printf("x%d=%.2lf\n", r + 1, matrix[r][n] / matrix[r][r]);
    }
    return 0;
}

通过求解矩阵的逆可以进行解线性方程组 x ⃗ = A − 1 b ⃗ \vec{x}=A^{-1}\vec{b} x =A1b

具体应用

  • 计算几何:通过向量、叉乘等运算去计算几何之间的关系
  • 概率论:马尔科夫链、计数、矩阵快速幂优化等
  • 多项式:多项式算法的基础
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值