线性代数
本文仅发布于此博客和作者的洛谷博客,不允许任何人以任何形式转载,无论是否标明出处及作者。
声明:由于这是一个OI里的线性代数讲解而不是数学的讲解,所以这篇文章只会出现和OI高度相关的内容。
矩阵基础
矩阵是一个按照长方阵列排列的数集。记矩阵
单位矩阵
定义单位矩阵
即除主对角线上元素为1外,其他位置上元素均为0的方阵。
零矩阵
定义零矩阵
全零的矩阵。
转置矩阵
定义的转置矩阵为
即对关于主对角线翻转。
虽然零矩阵和转置矩阵没什么用
矩阵加法
两个矩阵相加时,在每一位上相加即可。
使用代码可能更好理解。
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
c[i][j]=a[i][j]+b[i][j];
}
}
矩阵减法
同理。
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
c[i][j]=a[i][j]-b[i][j];
}
}
注意,行数和列数相同的矩阵才能做加减法。
矩阵数乘
矩阵的数乘和乘法不同。矩阵和数相乘,相当于的每一位和相乘。
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
c[i][j]=a[i][j]*k;
}
}
易得,上面的运算都满足交换律,结合律,复杂度均为
矩阵乘法
给出两个矩阵,相乘后得到一个矩阵,满足,即“左行右列”。
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
c[i][j]=0;
for(int k=1;p<=p;k++){
c[i][j]+=a[i][k]*b[k][j];
}
}
}
复杂度
注意,矩阵乘法必须保证的列数等于的行数,而且矩阵乘法并不满足交换律。
矩阵快速幂
方阵的次幂记为,
特殊地,为单位矩阵。
暴力做时,复杂度,显然过高,尝试对其进行优化。
可以使用快速幂的方法。下面是标准的快速幂代码:
//a^k mod P
while(k){
if(k&1){
ans*=a;
ans%=P;
}
a*=a;
a%=P;
k>>=1;
}
本质上是一个把次幂拆分成一些的倍数次幂相乘。如果连这个都看不懂了的话建议还是花点时间进行一次复健。
我们对矩阵求幂进行完全一模一样的操作即可,总复杂度乘上一个矩阵乘法,
矩阵快速幂和上面的东西可以一起集成在一个程序里:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int P=1e9+7;
void debug(){//不要在意这些细节
cout<<"???what the hell"<<endl;
exit(0);
}
int mod(int k){//严谨的取模
return ((k%P)+P)%P;
}
struct matrix{
int n;
int m;
int v[105][205];
matrix(int t1,int t2){//初始化全0
n=t1;
m=t2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
v[i][j]=0;
}
}
}
};
matrix I(int n){//构造单位矩阵
matrix c(n,n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
c.v[i][j]=0;
if(i==j){
c.v[i][j]=1;
}
}
}
return c;
}
matrix O(int n,int m){//构造零矩阵
matrix c(n,m);
return c;
}
matrix T(matrix c){//构造转置矩阵
for(int i=1;i<=max(c.n,c.m);i++){
for(int j=1;j<=i;j++){
swap(c.v[i][j],c.v[j][i]);
}
}
swap(c.n,c.m);
return c;
}
matrix operator+(matrix a,matrix b){//矩阵加
if(a.n!=b.n||a.m!=b.m){
debug();
}
matrix c(a.n,a.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=a.v[i][j]+b.v[i][j];
c.v[i][j]=mod(c.v[i][j]);
}
}
return c;
}
matrix operator-(matrix a,matrix b){//矩阵减
if(a.n!=b.n||a.m!=b.m){
debug();
}
matrix c(a.n,a.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=a.v[i][j]-b.v[i][j];
c.v[i][j]=mod(c.v[i][j]);
}
}
return c;
}
matrix operator*(matrix a,int b){//矩阵数乘
matrix c(a.n,a.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=a.v[i][j]*b;
c.v[i][j]=mod(c.v[i][j]);
}
}
return c;
}
matrix operator*(matrix a,matrix b){//矩阵乘
if(a.m!=b.n){
debug();
}
matrix c(a.n,b.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=0;
for(int k=1;k<=a.m;k++){
c.v[i][j]+=a.v[i][k]*b.v[k][j];
c.v[i][j]=mod(c.v[i][j]);
}
}
}
return c;
}
matrix pow(matrix a,int k){//矩阵快速幂
if(a.n!=a.m){
debug();
}
matrix c(a.n,a.m);
c=I(a.n);
while(k!=0){
if(k&1){
c=c*a;
}
a=a*a;
k>>=1;
}
return c;
}
void print(matrix c){
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
cout<<c.v[i][j]<<' ';
}
cout<<endl;
}
return;
}
signed main(){
return 0;
}
一个比较不是很全的矩阵全家桶就做好了。
这份代码先放放,处理一下矩阵加速。
矩阵加速
定义斐波那契数列,其中尝试在快于的时间内计算出。
构造一个矩阵,可以发现矩阵变成即仅需乘上一个,对于每次变换皆如此。
因此,而后可得
使用矩阵快速幂,即可在的复杂度下完成,所以整个过程为。
模板题过程相似,快速幂的矩阵变为了的而已。
另外,可以尝试想一下这道题。
给出个操作,对于每个操作,输入两个数,计算斐波那契数列上区间的数字和。斐波那契数列下标从开始,依次为.
对于的数据:保证 。
题解:
设斐波那契数列为,对于每个查询,通过矩阵加速递推地找到和。设另设数列特殊地,。仍然使用矩阵加速,在的时间内求,区间之和即为
对于每一次操作,复杂度为,总共为.
高斯消元
初等行变换
对于任意矩阵,可以进行三种初等行变换:
-
倍加:把某一行所有元素乘上某个数并加到一行。(原来那行不变)
-
倍乘:某一行乘上非零数。
-
对换:两行元素互换。
注意到,对换可以由倍加操作完成。(不会的去看CSP初赛学怎么不用辅助空间交换元素)(虽然这个性质并没有什么用处,,,)
解线性方程组
我们把给出的方程组变成矩阵的形式:
通过初等行变换把左边的方阵变换成单位矩阵,则就是未知数的值。
具体实现方面,对于第列,找到绝对值最小且非零的(为了少掉精度),把这一行和第行交换。然后用这一行消掉所有其他行的第个元素。
如果找不到非零,就无解。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct matrix{
int n;
int m;
double v[105][205];//别忘了改double
matrix(int t1,int t2){//初始化全0
n=t1;
m=t2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
v[i][j]=0;
}
}
}
};
signed main(){
int n;
cin>>n;
matrix c(n,n+1);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
cin>>c.v[i][j];
}
}
for(int j=1;j<=c.m-1;j++){//对每一列进行消元
double max_=-0x3f3f3f3f;
int maxpos=0;
for(int i=j;i<=c.n;i++){//找到j列最大的a[i][j](注意:j行之前的行已经是单位矩阵的形式了,不能碰)
if(c.v[i][j]>max_){
max_=c.v[i][j];
maxpos=i;
}
}
for(int k=1;k<=c.m;k++){//交换maxpos行和j行
swap(c.v[j][k],c.v[maxpos][k]);
}
if(c.v[j][j]==0){//无解判定
cout<<"No Solution"<<endl;
return 0;
}
for(int i=1;i<=c.n;i++){//对其他行进行消元
if(i==j){//当前行跳过
continue;
}
double tmp=c.v[i][j]/c.v[j][j];//第j行全体乘上tmp再减到当前行上
for(int k=1;k<=c.m;k++){
c.v[i][k]-=c.v[j][k]*tmp;
}
}
double tmp=c.v[j][j];
for(int k=1;k<=c.m;k++){//a[j][j]化为1
c.v[j][k]/=tmp;
}
}
for(int i=1;i<=c.n;i++){//快乐输出
cout<<fixed<<setprecision(2)<<c.v[i][c.m]<<endl;
}
return 0;
}
矩阵求逆
定义方阵的逆矩阵,使得。
的逆矩阵不一定存在。
以为例,逆矩阵的求法如下:
1.在的右边补上一个单位矩阵。
- 对左边进行高斯消元,消成单位矩阵。如果无法消元即代表逆矩阵不存在。
- 此时右半边就是逆矩阵了。
注意模板题是输出模意义下的逆矩阵,把除法全部换成逆元(费马小定理)即可。
行列式求值
方阵的行列式记为或,它具有一个数值,值的计算遵循一个比较复杂的公式。行列式也可以进行高斯消元,并且值也会相应变化。
-
倍加值不变。
-
倍乘值乘k。
-
对换(自己和自己换不算)值反号。
另外,“上三角矩阵”(即主对角线一下均为0)的行列式的值为对角线乘积。
根据这个,我们可以把矩阵消成“上三角矩阵”(消成单位矩阵一步到位也可以的)计算值就可以了。
实现时,当无法消元时,答案为0.
奉上一份常数爆炸根本过不去的全家桶(
(其实还会有WA,因为这道题模数不是质数。正解是在消元的时候进行“辗转相减”而不是直接逆元)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int P=1e9+7;
void debug(){
cout<<"???what the hell"<<endl;
exit(0);
}
int mod(int k){
return ((k%P)+P)%P;
}
int rev(int a){
int ans=1;
int k=P-2;
while(k!=0){
if(k&1){
ans*=a;
ans=mod(ans);
}
a*=a;
a=mod(a);
k>>=1;
}
return ans;
}
struct matrix{
int n;
int m;
int v[105][105];
matrix(int t1,int t2){
n=t1;
m=t2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
v[i][j]=0;
}
}
}
};
matrix I(int n){
matrix c(n,n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
c.v[i][j]=0;
if(i==j){
c.v[i][j]=1;
}
}
}
return c;
}
matrix O(int n,int m){
matrix c(n,m);
return c;
}
matrix T(matrix c){
for(int i=1;i<=max(c.n,c.m);i++){
for(int j=1;j<=i;j++){
swap(c.v[i][j],c.v[j][i]);
}
}
swap(c.n,c.m);
return c;
}
bool operator==(matrix a,matrix b){
if(a.n!=b.n||a.m!=b.m){
return false;
}
for(int i=1;i<=a.n;i++){
for(int j=1;j<=a.m;j++){
if(a.v[i][j]!=b.v[i][j]){
return false;
}
}
}
return true;
}
bool operator!=(matrix a,matrix b){
return !(a==b);
}
matrix operator+(matrix a,matrix b){
if(a.n!=b.n||a.m!=b.m){
debug();
}
matrix c(a.n,a.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=a.v[i][j]+b.v[i][j];
c.v[i][j]=mod(c.v[i][j]);
}
}
return c;
}
matrix operator-(matrix a,matrix b){
if(a.n!=b.n||a.m!=b.m){
debug();
}
matrix c(a.n,a.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=a.v[i][j]-b.v[i][j];
c.v[i][j]=mod(c.v[i][j]);
}
}
return c;
}
matrix operator*(matrix a,int b){
matrix c(a.n,a.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=a.v[i][j]*b;
c.v[i][j]=mod(c.v[i][j]);
}
}
return c;
}
matrix operator*(matrix a,matrix b){
if(a.m!=b.n){
debug();
}
matrix c(a.n,b.m);
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
c.v[i][j]=0;
for(int k=1;k<=a.m;k++){
c.v[i][j]+=a.v[i][k]*b.v[k][j];
c.v[i][j]=mod(c.v[i][j]);
}
}
}
return c;
}
matrix pow(matrix a,int k){
if(a.n!=a.m){
debug();
}
matrix c(a.n,a.m);
c=I(a.n);
while(k!=0){
if(k&1){
c=c*a;
}
a=a*a;
k>>=1;
}
return c;
}
pair<matrix,int> Gauss_jordan(matrix c){
int ans=1;//行列式求值专用
if(c.m<c.n){
debug();
}
for(int j=1;j<=c.n;j++){
int max_=-0x7fffffff;
int maxpos=0;
for(int i=j;i<=c.n;i++){
if(c.v[i][j]>max_&&c.v[i][j]!=0){
max_=c.v[i][j];
maxpos=i;
}
}
if(maxpos!=j){
ans*=-1;//换行就换号
ans=mod(ans);
}
for(int k=1;k<=c.m;k++){
swap(c.v[j][k],c.v[maxpos][k]);
}
if(c.v[j][j]==0){
matrix ERR(c.n,c.m);
return pair<matrix,int>{ERR,-0x7fffffff};
}
for(int i=1;i<=c.n;i++){
if(i==j){
continue;
}
int tmp=c.v[i][j]*rev(c.v[j][j]);
tmp=mod(tmp);
for(int k=1;k<=c.m;k++){
c.v[i][k]-=c.v[j][k]*tmp;
c.v[i][k]=mod(c.v[i][k]);
}
}
int tmp=rev(c.v[j][j]);
ans*=c.v[j][j];//行列式/k,答案*k
ans=mod(ans);
for(int k=1;k<=c.m;k++){
c.v[j][k]*=tmp;
c.v[j][k]=mod(c.v[j][k]);
}
}
return pair<matrix,int>{c,ans};
}
matrix rev(matrix a){
if(a.n!=a.m){
debug();
}
matrix c(a.n,a.m*2);//补单位矩阵
for(int i=1;i<=a.n;i++){
for(int j=1;j<=a.m;j++){
c.v[i][j]=a.v[i][j];
}
}
matrix tmp=I(a.n);
for(int i=1;i<=tmp.n;i++){
for(int j=1;j<=tmp.m;j++){
c.v[i][j+tmp.n]=tmp.v[i][j];
}
}
c=Gauss_jordan(c).first;//得到消元后矩阵
matrix ans(a.n,a.m);
for(int i=1;i<=a.n;i++){
for(int j=1;j<=a.m;j++){
a.v[i][j]=c.v[i][j+a.n];
}
}
return a;//拿到右半边
}
int det(matrix c){//直接集成在高斯消元里面算
return Gauss_jordan(c).second;
}
void scan(matrix &c){
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
cin>>c.v[i][j];
}
}
return;
}
void print(matrix c){
for(int i=1;i<=c.n;i++){
for(int j=1;j<=c.m;j++){
cout<<c.v[i][j]<<' ';
}
cout<<endl;
}
return;
}
signed main(){
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】