矩阵那些事

Fractures

2019-06-22 13:43:55

Personal

# 其实矩阵是一个非常有意思的东西呢 ## 矩阵在各个领域都能用到,而且非常方便呢 ### 那么,矩阵长什么样呢 #### 大体长这个样: $\begin{bmatrix}a_{1,1}&a_{1,2}&a_{1,3}&\cdots\\a_{2,1}&a_{2,2}&a_{2,3}&\cdots\\a_{3,1}&a_{3,2}&a_{3,3}&\cdots\\\vdots&\vdots&\vdots\end{bmatrix}$ ##### 当然,矩阵并不一定必须要是方阵 #### 那么,矩阵怎么运算? 其实矩阵的运算也挺简单的: 比如说: $\begin{bmatrix}10&7\\6&4\end{bmatrix}$ $+$ $\begin{bmatrix}6&1\\8&-3\end{bmatrix}$ $=$ $\begin{bmatrix}16&8\\14&1\end{bmatrix}$ 挺好看出来的吧?其实就是每个位置相对应的数相加就行了 减法也跟加法一样 矩阵乘法是跟加减不一样的,而乘法才是矩阵运算的核心 我们同样来看上一个栗子: $\begin{bmatrix}10&7\\6&4\end{bmatrix}$ $\times$ $\begin{bmatrix}6&1\\8&-3\end{bmatrix}$ $=$ $\begin{bmatrix}116&-11\\68&-6\end{bmatrix}$ 是不是跟上面的加减法很不一样呢 我们来仔细看一下这个式子: $\begin{bmatrix}10&7\\6&4\end{bmatrix}$ $\times$ $\begin{bmatrix}6&1\\8&-3\end{bmatrix}$ $=$ $\begin{bmatrix}10\times6+7\times8&10\times1+7\times-3\\6\times6+4\times8&6\times1+4\times-3\end{bmatrix}$ $=$ $\begin{bmatrix}116&-11\\68&-6\end{bmatrix}$ 如果我们设矩阵$A,B,C$且$A\times B=C$, 我们能看出来,矩阵乘法的原理是$A$的横行$\times$$B$的纵行的总和 #### 注:在实际应用中只会应用到矩阵的方阵,所以我们下面提到的矩阵都是方阵 代码实现也很简单(方阵): ```cpp for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ for(int k=1;k<=n;k++){ c.m[i][j]=c.m[i][j]+a.m[i][k]*b.m[k][j]; } } } ``` 我们也可以用数组存矩阵,但是那样用有局限性,这个在后面会提到 矩阵有几个特性,它支持结合律,所以它的乘法运算跟别的乘法运算差不多 说到这里,矩阵到底有什么用呢? 我们来看一下斐波那契数列: $f(n)=f(n-1)+f(n-2)$ 这个式子很简单,只要递推一下就可以了 **但是!** 如果我们要求第2000000000项,那么不用说,我们肯定会被卡得怀疑人生 这时候,矩阵就派上用场了 首先,$f_i=f_{i-1}+f_{i-2}=f_{i-1}\times1+f_{i-2}\times1$ 而$f_{i-1}=f_{i-1}\times1+f_{i-2}\times1$ $\therefore\begin{bmatrix}f_{i}&0\\f_{i-1}&0\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\times\begin{bmatrix}f_{i-1}&0\\f_{i-2}&0\end{bmatrix}$ 而$\begin{bmatrix}f_{i-1}&0\\f_{i-2}&0\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\times\begin{bmatrix}f_{i-2}&0\\f_{i-3}&0\end{bmatrix}$ 即$\begin{bmatrix}f_{i}&0\\f_{i-1}&0\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}^2\times\begin{bmatrix}f_{i-2}&0\\f_{i-3}&0\end{bmatrix}$ 最终,我们能推出$\begin{bmatrix}f_{i}&0\\f_{i-1}&0\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{i-2}\times\begin{bmatrix}f_{2}&0\\f_{1}&0\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{i-2}\times\begin{bmatrix}1&0\\1&0\end{bmatrix}$ 这样的话能比普通的递推式快很多很多很多,因为我们求矩阵快速幂是很快的 ### 那么,矩阵快速幂怎么求? 上面我说过,矩阵满足结合律,所以矩阵快速幂跟普通快速幂几乎一模一样,只是把乘法改成矩阵乘法而已 代码: ```cpp #include<cstdio> #include<iomanip> #include<cstring> #define ll long long #define mod 1000000007 ll n,p; struct Mat{ ll m[101][101];//存储矩阵的结构体数组 }; Mat a,e; Mat Mul(Mat x,Mat y){//矩阵乘法 Mat c; for(int i=1;i<=n;i++){//对于结构体我们不能使用memset for(int j=1;j<=n;j++){ c.m[i][j]=0; } } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ for(int k=1;k<=n;k++){ c.m[i][j]=c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod;//核心代码 } } } return c; } Mat MatPow(Mat x,ll y){//矩阵快速幂,可以发现除了乘法被换成Mat以外基本跟快速幂没有什么区别 Mat ans=e; while(y){ if(y&1){ ans=Mul(ans,x); } x=Mul(x,x); y>>=1; } return ans; } int main(){ scanf("%lld%lld",&n,&p); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ scanf("%lld",&a.m[i][j]); } } for(int i=1;i<=n;i++){//单位矩阵,这个相当于1,对角线都是1,其他的是0,其他矩阵乘以这个矩阵本身的值不变 e.m[i][i]=1; } Mat ans=MatPow(a,p); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ printf("%lld ",ans.m[i][j]%mod); } printf("\n"); } return 0; } ``` 从中我们就可以看到开结构体的优越性,而且我们开数组存矩阵的话根本就没办法进行运算 那么,我们的斐波那契数列就可以写出来了: ```cpp #include<cstdio> #include<cstring> #define ll long long #define mod 1000000007 int t; ll n; struct Mat{ ll m[4][4]; }; Mat a,e; inline Mat Mul(Mat x,Mat y){ Mat c; for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ c.m[i][j]=0; } } for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ for(int k=1;k<=3;k++){ c.m[i][j]=c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod; } } } return c; } Mat Matpow(Mat x,ll y){ Mat ans=e; while(y){ if(y&1){ ans=Mul(ans,x); } x=Mul(x,x); y>>=1; } return ans; } Mat aans; Mat d; int main(){ e.m[1][1]=1; e.m[2][2]=1; //e.m[3][3]=1; a.m[1][1]=1; a.m[2][1]=1; //a.m[3][1]=1; d.m[1][1]=1; d.m[2][1]=1; //d.m[3][2]=1; d.m[1][2]=1; //scanf("%d",&t); scanf("%lld",&n); if(n<=2){ printf("1\n"); return 0; } aans=Mul(Matpow(d,n-2),a); printf("%lld\n",aans.m[1][1]%mod); return 0; } ``` 也就是[这道题](https://www.luogu.org/problem/P1962) 那么,如果只这样呢? $f(1)=f(2)=f(3)=1$ $f(n)=f(n-1)+f(n-3)$ 我们可以这么推: 首先,$f_i=f_{i-1}+f_{i-3}=f_{i-1}\times1+f_{i-2}\times0+f_{i-3}\times1$ $f_{i-1}=f_{i-1}\times1+f_{i-2}\times0+f_{i-3}\times1$ $f_{i-2}=f_{i-1}\times0+f_{i-2}\times1+f_{i-3}\times0$ $\therefore\begin{bmatrix}f_{i}&0&0\\f_{i-1}&0&0\\f_{i-2}&0&0\end{bmatrix}=\begin{bmatrix}1&0&1\\1&0&0\\0&1&0\end{bmatrix}\times\begin{bmatrix}f_{i-1}&0&0\\f_{i-2}&0&0\\f_{i-3}&0&0\end{bmatrix}$ 而$\begin{bmatrix}f_{i-1}&0&0\\f_{i-2}&0&0\\f_{i-3}&0&0\end{bmatrix}=\begin{bmatrix}1&0&1\\1&0&0\\0&1&0\end{bmatrix}\times\begin{bmatrix}f_{i-2}&0&0\\f_{i-3}&0&0\\f_{i-4}&0&0\end{bmatrix}$ 即$\begin{bmatrix}f_{i}&0&0\\f_{i-1}&0&0\\f_{i-2}&0&0\end{bmatrix}=\begin{bmatrix}1&0&1\\1&0&0\\0&1&0\end{bmatrix}^2\times\begin{bmatrix}f_{i-2}&0&0\\f_{i-3}&0&0\\f_{i-4}&0&0\end{bmatrix}$ 最终,我们能推出$\begin{bmatrix}f_{i}&0&0\\f_{i-1}&0&0\\f_{i-2}&0&0\end{bmatrix}=\begin{bmatrix}1&0&1\\1&0&0\\0&1&0\end{bmatrix}^{i-3}\times\begin{bmatrix}f_{3}&0&0\\f_{2}&0&0\\f_{1}&0&0\end{bmatrix}=\begin{bmatrix}1&0&1\\1&0&0\\0&1&0\end{bmatrix}^{i-3}\times\begin{bmatrix}1&0&0\\1&0&0\\1&0&0\end{bmatrix}$ 所以这样我们就可以得出结果了 代码: ```cpp #include<cstdio> #include<cstring> #define ll long long #define mod 1000000007 int t; int n; struct Mat{ ll m[4][4]; }; Mat a,e; inline Mat Mul(Mat x,Mat y){ Mat c; for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ c.m[i][j]=0; } } for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ for(int k=1;k<=3;k++){ c.m[i][j]=c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod; } } } return c; } Mat Matpow(Mat x,ll y){ Mat ans=e; while(y){ if(y&1){ ans=Mul(ans,x); } x=Mul(x,x); y>>=1; } return ans; } Mat aans; Mat d; int main(){ e.m[1][1]=1; e.m[2][2]=1; e.m[3][3]=1; a.m[1][1]=1; a.m[2][1]=1; a.m[3][1]=1; d.m[1][1]=1; d.m[2][1]=1; d.m[3][2]=1; d.m[1][3]=1; scanf("%d",&t); for(int i=1;i<=t;i++){ scanf("%d",&n); if(n<=3){ printf("1\n"); continue; } aans=Mul(Matpow(d,n-3),a); printf("%lld\n",aans.m[1][1]%mod); } return 0; } ``` 也就是[这道题](https://www.luogu.org/problem/P1939) 终于讲完啦!