【矩阵】矩阵初级
Definition
矩阵是一个按照长方阵列排列的复数或实数集合。
描述一个矩阵的形状使用两个参数,为矩阵的行数和列数。在代码中是用r和c体现。
约定在描述矩阵时,行在前列在后,例如一个n*m的矩阵意味着一个n行m列的矩阵。
两个矩阵相同,当且仅当他们行、列数相同并且元素完全相同。
两个矩阵形状相同,当且仅当他们行、列数相同。
operation algorithm
addition
两个矩阵能够进行加法,当且仅当两个矩阵形状相同。
两个矩阵进行加法,返回一个矩阵,矩阵行、列数与原矩阵相同,对应位置上的值为两个矩阵对应位置上值的和。例如:
subtraction
类比两个矩阵进行加法,两个矩阵能进行减法,当且仅当他们形状相同。
两个矩阵进行减法,返回一个矩阵,矩阵行、列数与原矩阵相同,对应位置上的值为两个矩阵对应位置上值的差。例如:
multiplication
两个矩阵A,B能进行A*B的矩阵乘法,当且仅当A的列数等于B的行数。
注意,A*B ≠ B*A
下面约定矩阵A为乘号前的矩阵,矩阵B为乘号后的矩阵。
两个矩阵A,B进行乘法A*B,返回一个矩阵C,矩阵行数为A的行数;矩阵列数为B的列数。矩阵第i行第j列的值C(i,j)被定义如下:
C(i,j)=ΣA.ck=1 A(i,k)*B(k,j) 。其中A.c代表A的列数。
例如:
division
两个矩阵做除法A/B,求出B的逆矩阵B-1,则等价于A*B-1。求逆矩阵的方法过于复杂不表。
lows
矩阵加、减法具有交换律,结合率。
矩阵乘法具有结合率。即(A*B)*C=A*(B*C)。不具有交换律。即A*B*C!=A*C*B。
矩阵快速幂
方阵:一个矩阵时方阵当且仅当它的行数等于列数
主对角线:一个方阵的主对角线被定义为从左上角连接到右下角的对角线。
单位矩阵:一个方阵是单位矩阵当且仅当它有且仅有主对角线上元素的值为1,其他值为0。
性质:任何一个矩阵乘上一个能和他相乘的单位矩阵,得到的矩阵是它本身。
矩阵快速幂:
一个矩阵能进行矩阵快速幂当且仅当它是一个方阵。
矩阵进行快速幂与普通快速幂相同。必须使用循环形式。因为使用递归可能会占用较多资源。
矩阵优化类递推题目
需要矩阵优化的递推题目一般递推式中,该位置i的值一般前面第i-k项有关,其中k为若干个常数。例如fibnacci数列:fi=fi-1+fi-2。
这样的题目可以将fi到fi+x连续组成一个矩阵。其中x为k的最大值。设W为一个矩阵,解方程|fi,fi+1,…,fi+x|*W=|fi+1,fi+2,…,fi+x+1|。
解出W是一个常量矩阵,这样数学归纳易证f1*Wn-1=fn,可以求出第n项的值。
Codes
下面是封装了矩阵加、乘两则运算的代码
struct M { static const int Maxsize = 10; static const int MODNUM = 1000000007; int r,c; ll mat[Maxsize][Maxsize]; void clear() {memset(mat,0,sizeof mat);r=c=0;} bool can_mul(const M &_a,const M _b) { return _a.c==_b.r; } bool can_add(const M &_a,const M &_b) { return (_a.r==_b.r) and (_a.c==_b.c); } }; M operator+(const M &_a,const M &_b) { M _ans;_ans.clear(); for(rg int i=1;i<=_a.r;++i) for(rg int j=1;j<=_a.c;++j) _ans.mat[i][j]=(_a.mat[i][j]+_b.mat[i][j])%M::MODNUM; _ans.r=_a.r;_ans.c=_a.c; return _ans; } M operator*(const M &_a,const M &_b) { M _ans;_ans.clear(); for(rg int i=1;i<=_a.r;++i) for(rg int j=1;j<=_b.c;++j) for(rg int k=1;k<=_a.c;++k) _ans.mat[i][j]=(_ans.mat[i][j]+_a.mat[i][k]*_b.mat[k][j])%M::MODNUM; _ans.r=_a.r;_ans.c=_b.c; return _ans; }
Examples
P3390 【模板】 矩阵快速幂
Description
给定n*n的矩阵A,求A^k
Input
第一行,n,k
第2至n+1行,每行n个数,第i+1行第j个数表示矩阵第i行第j列的元素
Output
输出A^k
共n行,每行n个数,第i行第j个数表示矩阵第i行第j列的元素,每个元素模10^9+7
Sample Input
2 1 1 1 1 1
Sample Output
1 1 1 1
Hint
n<=100, k<=10^12, |矩阵元素|<=1000
Soltion
板子题要啥Solution
Code
#include<cstdio> #include<cstring> #define rg register #define ci const int #define cl const long long int typedef long long int ll; namespace IO { char buf[100]; } template <typename T> inline void qr(T &x) { char ch=getchar(),lst=' '; while(ch>'9'||ch<'0') lst=ch,ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); if(lst=='-') x=-x; } template <typename T> inline void write(T x,const char aft,const bool pt) { if(x<0) {putchar('-');x=-x;} int top=0; do { IO::buf[++top]=x%10+'0'; x/=10; }while(x); while(top) putchar(IO::buf[top--]); if(pt) putchar(aft); } template <typename T> inline T mmax(const T _a,const T _b) {if(_b<_a) return _a;return _b;} template <typename T> inline T mmin(const T _a,const T _b) {if(_a>_b) return _b;return _a;} template <typename T> inline T mabs(const T _a) {if(_a<0) return -_a;return _a;} template <typename T> inline void mswap(T &_a,T &_b) { T _temp=_a;_a=_b;_b=_temp; } const int maxn = 110; const int MOD = 1e9+7; struct M { ll mat[maxn][maxn]; void clear() { memset(mat,0,sizeof mat); } }; M MU,re,ans; int n;ll k; void mul(const M &_a,const M & _b,M &_c) { M _temp;_temp.clear(); for(rg int i=1;i<=n;++i) { for(rg int j=1;j<=n;++j) { for(rg int k=1;k<=n;++k) _temp.mat[i][j]=(_temp.mat[i][j]+_a.mat[i][k]*_b.mat[k][j]%MOD)%MOD; } } for(rg int i=1;i<=n;++i) for(rg int j=1;j<=n;++j) _c.mat[i][j]=_temp.mat[i][j]; } int main() { qr(n);qr(k); for(rg int i=1;i<=n;++i) for(rg int j=1;j<=n;++j) { M &now=MU;qr(now.mat[i][j]); } for(rg int i=1;i<=n;++i) ans.mat[i][i]=1; while(k) { if(k&1) mul(ans,MU,ans); mul(MU,MU,MU); k>>=1; } for(rg int i=1;i<=n;++i) { for(rg int j=1;j<n;++j) write(ans.mat[i][j],' ',true); write(ans.mat[i][n],'\n',true); } }
【P1962】 斐波那契数列
Description
求斐波那契数第n项。其中规定f0=0,f1=1
Input
一个数,n
Output
一个数,答案取模1e9+7。
Sample Input
10
Sample Output
55
Hint
n在long long范围内
Solution
因为知道数列中连续两项就可以推出后面所有的数,所以考虑把两个连续的数放到一个矩阵中。
考虑|fi,fi+1|*X=|fi+1,fi+2|。记A=|fi,fi+1|,B=|fi+1,fi+2|。r代表行数,c代表列数。
由矩阵乘法的性质可知,A.c=X.r;B.c=X.c。所以X是二行二列矩阵
设
根据矩阵的乘法法则,可以列出方程:
a*fi+c*fi+1=fi+1①
b*fi+d*fi+1=fi+2②
直接利用①两侧相等解出a=0,c=1。
对于方程②使用递推式解得b=d=1。
这样矩阵x就被解出。
最后答案即是|f1,f2|*Xn-1。
Code
#include<cstdio> #include<cstring> #define rg register #define ci const int #define cl const long long int typedef long long int ll; namespace IO { char buf[100]; } template <typename T> inline void qr(T &x) { char ch=getchar(),lst=' '; while(ch>'9'||ch<'0') lst=ch,ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); if(lst=='-') x=-x; } template <typename T> inline void write(T x,const char aft,const bool pt) { if(x<0) {putchar('-');x=-x;} int top=0; do { IO::buf[++top]=x%10+'0'; x/=10; }while(x); while(top) putchar(IO::buf[top--]); if(pt) putchar(aft); } template <typename T> inline T mmax(const T _a,const T _b) {if(_b<_a) return _a;return _b;} template <typename T> inline T mmin(const T _a,const T _b) {if(_a>_b) return _b;return _a;} template <typename T> inline T mabs(const T _a) {if(_a<0) return -_a;return _a;} template <typename T> inline void mswap(T &_a,T &_b) { T _temp=_a;_a=_b;_b=_temp; } const int MOD = 1000000007; struct M { static const int Maxsize = 10; static const int MODNUM = 1000000007; int r,c; ll mat[Maxsize][Maxsize]; void clear() {memset(mat,0,sizeof mat);r=c=0;} bool can_mul(const M &_a,const M _b) { return _a.c==_b.r; } bool can_add(const M &_a,const M &_b) { return (_a.r==_b.r) and (_a.c==_b.c); } }; M operator+(const M &_a,const M &_b) { M _ans;_ans.clear(); for(rg int i=1;i<=_a.r;++i) for(rg int j=1;j<=_a.c;++j) _ans.mat[i][j]=(_a.mat[i][j]+_b.mat[i][j])%M::MODNUM; _ans.r=_a.r;_ans.c=_a.c; return _ans; } M operator*(const M &_a,const M &_b) { M _ans;_ans.clear(); for(rg int i=1;i<=_a.r;++i) for(rg int j=1;j<=_b.c;++j) for(rg int k=1;k<=_a.c;++k) _ans.mat[i][j]=(_ans.mat[i][j]+_a.mat[i][k]*_b.mat[k][j])%M::MODNUM; _ans.r=_a.r;_ans.c=_b.c; return _ans; } M fib,beg,MS; ll n; void beginning(); M pow(ll); int main() { beginning(); qr(n); if(n<3ll) return printf("1\n")&0; M _ans=pow(n-1); fib=beg*_ans; write(fib.mat[1][1],'\n',true); return 0; } void beginning() { beg.r=1;beg.c=2; beg.mat[1][2]=beg.mat[1][1]=1; MS.r=MS.c=2; MS.mat[1][2]=MS.mat[2][1]=MS.mat[2][2]=1; } M pow(ll x) { M _ans;_ans.clear();_ans.r=2;_ans.c=2; _ans.mat[1][1]=_ans.mat[2][2]=1; while(x) { if(x&1) _ans=_ans*MS; MS=MS*MS; x>>=1; } return _ans; }
Summary
这是矩阵中比较基础的部分,事实上,对于循环形式的快速幂,还是要好好掌握滴。
行列式真tm变态不会不会滚粗了