矩阵基础

矩阵基础

矩阵是线性代数中一个非常重要的内容,也是 NOIP 的考点之一。

定义

对于矩阵 \(A\),主对角线是指 \(A_{i,i}\) 的元素。

0元:所有位置均为 0 的矩阵。

1元(单位矩阵):主对角线上为 1,其余位置为 0 的矩阵。

单位矩阵之所以是主对角线,是因为一个矩阵在乘上单位矩阵之后是它本身,这就符合我们在学数的基本运算时,一个数乘它本身等于它本身。

实现代码:

struct mat//定义一个矩阵
{
    int v[maxn][maxn];//v[i][j]表示矩阵第 i 行第 j 列的元素
    int n;
    mat(){memset(v,0,sizeof(v));}//初始化一个空矩阵为0元
    mat(int N,int one=0)//初始化矩阵的大小并定义1元。
    {
        n=N;memset(v,0,sizeof(v));
        if(one)for(int i=1;i<=n;i++)v[i][i]=1;//如果定义了单位矩阵,则将主对角线初始化为1。
    }
};

运算

加法/减法

矩阵的加法/减法定义为把两个矩阵对应位置上的数相加减,即 \(C=A \pm B \Leftrightarrow \forall i \in [1,n],\forall j \in [1,m],C_{i,j} = A_{i,j} \pm B_{i,j}\),其中 \(A,B,C\) 都是 \(n \times m\) 矩阵。

乘法

\(A\)\(n \times m\) 矩阵,\(B\)\(m \times p\) 矩阵,则 \(C=A \times B\)\(n \times p\) 矩阵,并且对于 \(\forall i \in [1,n],\forall j \in [1,p]\)

\[C_{i,j}=\sum \limits_{k=1}^{m} A_{i,k} \times B_{k,j} \]

条件:参与矩阵运算的第一个矩阵的列数必须等于第二个矩阵的行数

所以结果矩阵 \(C\) 的第 \(i\) 行第 \(j\) 列的数,是由矩阵 \(A\) 的第 \(i\) 行的 \(m\) 个数与矩阵 \(B\) 的第 \(j\) 列的 \(m\) 个数分别相乘再相加得到。

代码实现:

mat operator*(mat A,mat B)//利用重载运算符重载矩阵乘法
{
    mat C=mat(A.n);//C表示 A*B 的结果,由于这里A和B都是方阵,所以结果矩阵的大小和A的大小相同。
    //注意:如果A和B并非方阵,则不能这么定义,而是应该定义为A的行数乘B的列数。
    for(int i=1;i<=C.n;i++)
    for(int j=1;j<=C.n;j++)
    for(int k=1;k<=C.n;k++)
        (C.v[i][j]+=A.v[i][k]*B.v[k][j])%=mod;    
    return C;      
}

运算律

矩阵乘法不满足交换律,因为对于矩阵 \(A\)\(B\)\(p \ne n\)

满足结合律,即 \((A \times B) \times C=A \times (B \times C)\)

满足分配律,即 \((A+B) \times C=A \times C + B \times C\)

我们可以利用矩阵的结合律,来实现矩阵快速幂。

矩阵快速幂

问题模型:对于一个矩阵 \(A\),求它的 \(k\) 次幂的结果,即 \(A^k\)

首先很容易想到一个 \(O(k)\) 的做法——暴力模拟。

是否有优化做法呢?

联想到我们学习快速幂的做法,能否同样运用到矩阵中去呢?

答案是肯定的。

我们可以发现,矩阵中的快速幂其实和数中的快速幂完全一样。因为矩阵的性质和数的性质具有相似性。

实现代码:

mat qpow(mat A,int T)
{
    mat ret=mat(A.n,1);//在普通快速幂中我们是初始化为1,这里初始化为单位矩阵。
    while(T)
    {
        if(T&1)ret=ret*A;
        A=A*A;T>>=1;
    }
    return ret;
}

十进制矩阵快速幂

和普通的十进制快速幂实际上也是一样的(已经通过倍增优化):

mat qpow(mat A,string T)
{
    mat ret=mat(A.n,1),t[20];
    t[1]=A;for(int i=2;i<=9;i++)t[i]=t[i-1]*A;//预处理出A^2到A^9,避免时间复杂度爆炸
    int l=T.size()-1;
    for(int i=0;i<=l;i++)
    {
        if(i)//ret^10,这里通过倍增优化。
        {
            mat x=ret*ret;//x=ret^2
            mat y=x*x;//y=ret^4
            mat z=y*y;//z=ret^8
            ret=z*x;//ret'=(ret^8)*(ret^2)=z*x;
        }
        int now=T[i]-'0';
        if(now)ret=ret*t[now];//注意特判now!=0
    }
    return ret;
}

应用

矩阵加速递推

放一些例题和我写的总结吧。这里的总结其实不算是题解,只是我自己的一个分析和总结。(还没写完/kk)

P1962 斐波那契数列

总结

P1397 [NOI2013] 矩阵游戏
总结

P3758 [TJOI2017]可乐
总结


高斯消元

高斯消元是一类求解线性方程组的方法。由 \(M\)\(N\) 元一次方程组共同构成的方程组叫线性方程组。

我们这里通过矩阵的方式来表示方程组,并进行操作,最后解出方程。

消元法

思想

消元法是高斯消元的前置知识。主要用于二元一次方程组的求解。

消元法是将方程组中的一方程的未知数用含有另一个未知数的代数式表示,并将其代入到另一个方程中去,消掉其中一个未知数,转化成一元一次方程,得到一解;或将方程组中的一方程倍乘某个常数加到另外一方程中去,也可以达到消去其中一个未知数的目的。

核心

  1. 两方程互换,解不变;
  2. 一方程乘非零\(k\),解不变;
  3. 一方程乘数 \(k\) 加上另一方程,解不变。

高斯消元法

思想

通过初等行变换把增广矩阵变为简化阶梯矩阵的线性方程组求解算法就是高斯消元算法。——《算法竞赛进阶指南》

德国数学家高斯对消元法进行了思考分析,得出了以下结论:

  • 在消元法中,参与计算和发生改变的是方程中各变量的系数;

  • 各变量并未参与计算,且没有发生改变;

  • 可以利用系数的位置表示变量,从而省略变量;

  • 在计算中将变量简化省略,方程的解不变。

介于以上四点,我们可以将线性方程组的所有系数写成一个 \(M\)\(N\) 列的“系数矩阵”,再加上没个方程等号右侧的常数,可以写成一个 \(M\)\(N+1\) 列的“增广矩阵”。那么,我们就可以完全忽略掉原方程组的变量,而只考虑系数的变换。

为了便于理解,这里给出一个例子:

\[\begin{cases}x_1+2x_2-x_3=-6\\2x_1+x_2-3x_3=-9\\-x_1-x_2+2x_3=7\end{cases}\implies\begin{bmatrix}1&2&-1&-6\\2&1&-3&-9\\-1&-1&2&7\end{bmatrix} \]

其中最后一列为常数。

看到这样的方程组,如果我们人工随意的去解方程,肯定可以解的出来。但是我们并不知道如何用代码实现我们所想的,原因就是我们在解方程的时候实际上并没有确定的顺序,而是较为灵活。但是计算机理解不了人灵活的思想,它只能按照固定的步骤和程序去做一件事。所以就需要我们规定一个消元的一般步骤和顺序。

下面我们就利用消元法的三个核心来详解高斯消元的一般方法。

求解

首先我们考虑,我们在经过对矩阵的变换之后,结果矩阵应该是什么样。

也就是说,当矩阵变换成什么样的时候,方程组就被我们解出来了。

从方程组的角度考虑,当我们解出来 \(x_1=t_1,x_2=t_2,…,x_n=t_n\),其中 \(t_1,t_2,…,t_n\) 为常数,这样的结果时,我们就解出来了方程组。

站在增广矩阵的角度看,也就是我们需要我们得到形如下面的矩阵:

\[\begin{bmatrix}1&0&0&t_1\\0&1&0&t_2\\0&0&1&t_3\end{bmatrix} \]

(这里以三元一次方程组为例)

也就是说,我们的结果矩阵应该是一个系数矩阵(注意这里是系数矩阵,而不包含最后一列的常数)的主对角线上都是 1,最后一列为常数的矩阵。

那么怎么样才能得到这样一个矩阵呢?

我们从第一行开始一行一行考虑。

要使第一行第一个元素是 1,那么对于原矩阵的第一行,我们可以将它整个除以 \(a_{1,1}\),根据消元法的第二个核心性质,这样做不影响答案。

要注意的一点是,如果 \(a_{1,1}\)本身就是 0,那么不能这么做。所以我们刚开始需要找到所有行中这个位置的元素不为 0 的放在第一行。

那么我们怎么样才能使这一行的其它位置变为 0 呢?

首先直接减是不可能的,因为不满足消元法最基本的三个核心性质。

似乎直接找不到思绪。

别急,我们先接着往下考虑。假设我们现在已经将第一行其它位置变为 0。

那么我们接着考虑第二行。

类比第一行,第二行要找的应该是第二列不为 0 的一行,然后将它换到第二行来,再对这一行的所有元素除以 \(a_{2,2}\)

第三行应该找的是第三列不为 0 的一行,然后将它换到第三行来,再对这一行的所有元素除以 \(a_{3,3}\)……依次类推,到了第 \(n\) 行,这时候只剩下一行,我们只需要对这一行的所有元素除以 \(a_{n,n}\)

这时候,我们再回过头来考虑,如何把第一行除了第一列之外的元素变为 0 的问题。

站在整个矩阵的角度可以知道,我们其实不仅要把第一行除了 \(a_{1,1}\) 之外的元素变为 0,还要把对于所有的第 \(i\) 行,所有除了 \(a_{i,i}\) 之外的元素变为 0。

从行的角度没有思路,我们可以换一个角度,从列的角度考虑。

我们如何将第 \(i\) 列除了第 \(i\) 个元素都变为 0 呢?

又可以联想到消元法的第三条核心性质,我们可以直接在把第 \(i\) 行元素除以 \(a_{i,i}\) 的同时,把其它行在这一列上的数全变为 0。

那么怎么变呢?

我们可以对每一行的每一个元素减去 \(a_{j,i}\times a_{i,k}\),即可达到目的。

这样我们高斯消元的全部过程就结束了。

总结一下具体步骤。

步骤

  1. for 每一行 \(i\)

  2. 对于从 \(i\)\(n\) 的每一行,找到第一个第 \(i\) 列不为 0 的行,把它换到第 \(i\) 行;

  3. 对第 \(i\) 行的所有元素除以 \(a_{i,i}\)

  4. 对于第 \(j\)\((j \ne i)\) 的每一个位置上的数 \(a_{j,k}-=a_{j,i}\times a_{i,k}\)

多解与无解

我们假设,系数为 \(a\),常数为 \(b\)

  • 无解:存在一行 \(a\) 全是 0,\(b\) 不为 0。

  • 多解:至少一行 \(a,b\) 均为0。假设有 \(n\)\(a,b\) 全为 0,那么原方程有 \(2^n\) 组解。

代码实现

//注意不要弄混 i,j,k
void gauss()
{
    for(int i=1;i<=n;i++)
    {
        bool f=false;
        for(int j=i;j<=n;j++)
        {
            if(a[j][i])
            {
                f=true;
                for(int k=1;k<=m;k++)swap(a[j][k],a[i][k]);
                double tt=a[i][i];
                for(int k=1;k<=m;k++)a[i][k]/=tt;
                break;
            }
        }
        if(!f){cout<<"No Solution"<<'\n';exit(0);}
        for(int j=1;j<=n;j++)
        {
            if(j==i)continue;
            double tt=a[j][i];
            for(int k=1;k<=m;k++)a[j][k]-=tt*a[i][k];
        }
    }
    for(int i=1;i<=n;i++)cout<<fixed<<setprecision(2)<<a[i][m]<<endl;
}

应用

给出一个最基本的应用的例子:P4035 [JSOI2008]球形空间产生器 【题解】

高斯消元法解异或方程组

高斯消元解异或方程组相当于是解模 2 意义下的多元一次方程组。

与解普通多元一次方程组的区别:由于异或方程组的所有位置上的值一定是 0 或 1,所以不需要进行“乘除法消元“,只需要”异或消元“。

P2962 [USACO09NOV]Lights G 【题解】


感觉矩阵这块,知识点很少,但是题做起来却没有做数论那么顺手。其实是我太菜了

所以主要的总结都放在题目总结里面了。知识点总结相对较少。

感觉需要重开 DP,这块但凡遇到点跟 DP 结合的题,我就能挂好久。(大概后面会系统总结一遍 dp 吧。)

posted @ 2022-04-10 15:07  向日葵Reta  阅读(495)  评论(0编辑  收藏  举报