数论 矩阵
矩阵
定义
由 个数 排成的 行 列的数表称为 行 列的矩阵,简称 矩阵。记作:
这 个数称为矩阵 的元素,简称元,数 位于矩阵 的第 行第 列
这个矩阵还可以简记为:
特殊矩阵
-
行列均为 的矩阵称为 阶方阵(也叫 阶矩阵)
-
只有一行的矩阵称为行矩阵(行向量)
-
只有一列的矩阵称为列矩阵(列向量)
-
若两个矩阵的行数和列数分别相同,则称这两个矩阵为同型矩阵
-
元素全为 的矩阵称为零矩阵,记作 。
- 不同阶数的零矩阵不相同
-
除主对角线外的元素均为 的矩阵称为对角矩阵
-
主对角线的元素为 ,其他元素都为 的 阶矩阵称为单位矩阵
-
若两个同型矩阵的对应元素相等,则两个矩阵相等
运算
矩阵加减
- 只有两个同型矩阵才可以做加减法
- 性质
- 交换律:
- 结合律:
矩阵乘法
- 设 ,当且仅当 时,两个矩阵可以以 的形式相乘
- 性质
- 不满足交换律
- 结合律:
- 左分配律:
- 右分配律:
- 数乘:
代码实现
int a[maxn][maxn], b[maxn][maxn], c[maxn][maxn];
int main(){
int m,n,k;
scanf("%d%d%d", &m, &n, &k);
for(int i = 1; i <= m; ++i)
for(int j = 1; j <= n; ++j)
scanf("%d", &a[i][j]);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= k; ++j)
scanf("%d", &b[i][j]);
for(int i = 1; i <= m; ++i)//枚举a的行数m
for(int j = 1; j <= k; ++j)//枚举b的列数k
for(int kk = 1; kk <= n; ++kk)//枚举a的列(b的行)
c[i][j] += a[i][kk]*b[kk][j];
return 0;
}
高斯消元
定义
以下内容引自wikipedia
高斯消元法(Gaussian Elimination)是数学上线性代数中的一个算法,可以把矩阵转化为行阶梯形矩阵。高斯消元法可用来为线性方程组求解,求出矩阵的秩,以及求出可逆方阵的逆矩阵
该方法以数学家卡尔·高斯命名,但最早出现于《九章算术》
行阶梯形矩阵:满足下列条件的矩阵被称为行阶梯形矩阵:
- 元素不全为零的行(非零行)在所有元素全为零的行(全零行)的上面,即全零行都在矩阵的底部
- 非零行的首项系数,也称作主元,即最左边的首个非零元素,严格地比上面行的主元靠右
- (前两条的推论)首项系数所在的列,在该首项系数下面的元素都是零
例:下面的这个 矩阵是行阶梯形矩阵:
矩阵的秩:在线性代数中,一个矩阵 的列秩是 的线性无关的纵列的极大数目。类似地,行秩是 的线性无关的横行的极大数目。同一行秩和列秩总是相等的,因此可以简单地称为矩阵 的秩,写作
例:在下面这个 矩阵中:
不难发现第二纵列是第一纵列的两倍,而第四纵列等于第一和第三纵列的总和。第一和第三纵列是线性无关的,所以 的秩是 ,即
-
性质:一个矩阵的行秩和列秩总是相等的(证明)
-
性质:矩阵的初等变换不改变矩阵的秩
逆矩阵:(又称乘法反方阵、反矩阵)在线性代数中,给定一个 阶方阵 ,若存在一个 阶方阵 ,使得 ,其中 是 阶单位矩阵,则称 是可逆的,且 是 的逆矩阵,记作
- 只有方阵( 的矩阵)才可能有逆矩阵
- 若 的逆矩阵存在,则称 为非奇异方阵或可逆方阵
补充概念:
系数矩阵、增广矩阵:
设有一个方程组 :
那么下面的这个 的矩阵称为上面的方程组的系数矩阵:
而增广矩阵就是在系数矩阵的右边添上一列,这一列是线性方程组的等号右边的值:
基本原理
已知线性方程组:
这个方程组可以变换为如下形式:
再将 回代,我们可以依次解出 的值
在上述过程中,方程组 的增广矩阵变化如下:
我们可以发现,这个过程实际上就是矩阵的初等变换,将一个增广矩阵变换为了一个行阶梯形矩阵。之后再将已知的答案一个个地带入已经被简化的等式中的未知数中,就可以求出其余的答案了。
方程组解的判断
无解
当矩阵中出现某一行形如 ,即此时有 ,显然矛盾,方程组无解
唯一解
当行阶梯形矩阵形成严格的上(下)三角阵,或者可以化为 阶最简矩阵,那么有唯一解
注: 阶最简矩阵(简化行阶梯形矩阵):用上面的例子,上面的矩阵的 阶最简矩阵为:
无穷解
当矩阵中出现至少一行形如 时,有无穷解
每出现一个全零行就有一个自由元,有 个全零行就有 个自由元
代码
e.g. 高斯消元(模板)
描述
已知 元线性一次方程组 :
求 的解,若不存在唯一解输出 No Solution
I/O
Input: 第一行一个正整数
之后 行每行 个整数,分别代表
Output: 若存在唯一解,输出共 行,每行一个数 (保留两位小数)
若不存在唯一解输出一行 No Solution
范围
解法
#include<bits/stdc++.h>
using namespace std;
#define GO(u,v,i) for(register int i=u;i<=v;i++)
template<class t>inline t fr(){
register t num=0,dis=1;
register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
return num*dis;
}
template<class t>inline void fw(t num){
if(num>9)fw(num/10);
putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
if(num<0)num=-num,putchar('-');
fw(num);putchar(ch);
}
typedef double lf;
const int maxn=100+12;
const lf eps=1e-6;//误差
lf a[maxn][maxn];//增广矩阵
int n;//题目条件
//推荐阅读顺序:main()->gauss()->prnt()
inline bool gauss(lf a[][maxn],int n){
for(int i=1,maxi;i<=n;i++){//依次遍历n个方程
maxi=i;//记录第i列i+1行到n行间系数绝对值最大行
GO(i+1,n,j)if(fabs(a[j][i])>fabs(a[maxi][i]))maxi=j;
//若第i~n行的第i列均为0,说明方程组有无穷解或无解
if(fabs(a[maxi][i])<eps)return false;
if(maxi^i)GO(1,n+1,j)swap(a[i][j],a[maxi][j]);//交换两行
//消元
GO(i+1,n,j){
lf tmp=a[j][i]/a[i][i];
if(fabs(a[j][i])>eps)GO(i,n+1,k)a[j][k]-=tmp*a[i][k];
}
return true;
}
inline void prnt(lf a[][maxn],int n){
//求解输出
for(int i=n;i>0;i--){//回代求解
GO(i+1,n,j)a[i][n+1]-=a[j][n+1]*a[i][j];//a[i][i]已化为1
a[i][n+1]/=a[i][i];
if(fabs(a[i][n+1])<eps)a[i][n+1]=0;//防负
}
GO(1,n,i)printf("%.2lf\n",a[i][n+1]);
}
signed main(){
n=fr<int>();
GO(1,n,i)GO(1,n+1,j)scanf("%lf",&a[i][j]);
if(gauss(a,n))prnt(a,n);
else printf("No Solution\n");
return 0;
}
高斯-约旦消元法
高斯-约旦消元法(亦称高斯-若尔当消元法)在高斯消元法的基础上,将有唯一解的矩阵进一步约简为简化行阶梯形矩阵,因而无需回代求解。
#include<bits/stdc++.h>
using namespace std;
#define GO(u,v,i) for(register int i=u;i<=v;i++)
template<class t>inline t fr(){
register t num=0,dis=1;
register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
return num*dis;
}
template<class t>inline void fw(t num){
if(num>9)fw(num/10);
putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
if(num<0)num=-num,putchar('-');
fw(num);putchar(ch);
}
typedef double lf;
const int maxn=100+12;
const lf eps=1e-6;//误差
lf a[maxn][maxn];//增广矩阵
int n;//题目条件
//推荐阅读顺序:main()->gauss()->prnt()
inline bool gauss(lf a[][maxn],int n){
for(int i=1,maxi;i<=n;i++){
//枚举每个方程
maxi=i;
GO(i+1,n,j)if(fabs(a[j][i])>fabs(a[maxi][i]))maxi=j;
if(fabs(a[maxi][i])<eps)return false;//没有唯一解
if(i^maxi)//fabs(a[i][i])非最大,交换
GO(i,n+1,j)swap(a[i][j],a[maxi][j]);
lf tmp=a[i][i];//抽出系数,否则变成1以后就没法变了
GO(i,n+1,j)a[i][j]/=tmp;//第i行除以a[i][i],变成1
GO(1,n,j){
if(i!=j){
tmp=a[j][i];
GO(i,n+1,k)a[j][k]-=tmp*a[i][k];
}
}
}
return true;
}
inline void prnt(lf a[][maxn],int n){
//输出
GO(1,n,i)printf("%.2lf\n",a[i][n+1]);
}
signed main(){
n=fr<int>();
GO(1,n,i)GO(1,n+1,j)scanf("%lf",&a[i][j]);
if(gauss(a,n))prnt(a,n);
else printf("No Solution\n");
return 0;
}
高斯-约旦消元法的常数稍大
e.g. 球形空间产生器
题目传送门:P4035 (省选/NOI-)
这道题的难点在于如何将这个连等式转化为方程
我们假设球心的坐标为 ,给出的点的坐标依次为
根据题目提示,很容易想到以下等式:
我们拆分出其中的两个多项式:
开括号
观察到等式两边都有 ,消去,同时将常数项移至等式右边,将未知数移至等式左边
可以发现,对于这 个连等的多项式,我们可以每次取出其中的相邻的两个多项式,然后仿照上面的方法化简,最终得到 个等式构成的 元一次方程组
而解这个方程组则可以使用高斯消元法
#include<bits/stdc++.h>
using namespace std;
#define GO(u,v,i) for(register int i=u;i<=v;i++)
template<class t>inline t fr(){
register t num=0,dis=1;
register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
return num*dis;
}
template<class t>inline void fw(t num){
if(num>9)fw(num/10);
putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
if(num<0)num=-num,putchar('-');
fw(num);putchar(ch);
}
typedef double lf;
const int maxn=10+12;
const lf eps=1e-6;
int n;
lf a[maxn][maxn];
#include<bits/stdc++.h>
using namespace std;
#define GO(u,v,i) for(register int i=u;i<=v;i++)
template<class t>inline t fr(){
register t num=0,dis=1;
register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
return num*dis;
}
template<class t>inline void fw(t num){
if(num>9)fw(num/10);
putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
if(num<0)num=-num,putchar('-');
fw(num);putchar(ch);
}
typedef double lf;
const int maxn=100+12;
const lf eps=1e-6;//误差
lf a[maxn][maxn];//增广矩阵
int n;//题目条件
//推荐阅读顺序:main()->gauss()->prnt()
inline bool gauss(lf a[][maxn],int n){
for(int i=1,maxi;i<=n;i++){
//枚举每个方程
maxi=i;
GO(i+1,n,j)if(fabs(a[j][i])>fabs(a[maxi][i]))maxi=j;
if(fabs(a[maxi][i])<eps)return false;//没有唯一解
if(i^maxi)//fabs(a[i][i])非最大,交换
GO(i,n+1,j)swap(a[i][j],a[maxi][j]);
lf tmp=a[i][i];//抽出系数,否则变成1以后就没法变了
GO(i,n+1,j)a[i][j]/=tmp;//第i行除以a[i][i],变成1
GO(1,n,j){
if(i!=j){
tmp=a[j][i];
GO(i,n+1,k)a[j][k]-=tmp*a[i][k];
}
}
}
return true;
}
inline void prnt(lf a[][maxn],int n){
//输出
GO(1,n,i)printf("%.2lf\n",a[i][n+1]);
}
signed main(){
n=fr<int>();
GO(1,n,i)GO(1,n+1,j)scanf("%lf",&a[i][j]);
if(gauss(a,n))prnt(a,n);
else printf("No Solution\n");
return 0;
}
signed main(){
n=fr<int>();
GO(1,n+1,i)GO(1,n,j)scanf("%lf",&a[i][j]);
GO(1,n,i){
GO(1,n,j){//常数项
a[i][n+1]-=a[i][j]*a[i][j];
a[i][n+1]+=a[i+1][j]*a[i+1][j];
}
GO(1,n,j){//各项系数
a[i][j]=-2*a[i][j];
a[i][j]+=2*a[i+1][j];
}
}
gauss(a,n);//高斯消元
prnt(a,n);//求解输出
return 0;
}
判断无解与无穷解
常规的高斯消元与高斯-约旦消元在遇到无解和无穷解时都会直接结束,无法进行判断。那要如何判断无解和无穷解呢?
根据上文推导,在把增广矩阵消解成上三角型的行阶梯形矩阵后,若存在 时有无穷解,若存在一行 时无解,但用正常的消解法无法做出准确判断,例如下面的矩阵:
这是一个行阶梯形矩阵,其中 ,但 ,判断方程无解,但实际上是无穷解
我们交换这个增广矩阵的第一列和第六列,这样不会影响方程的解
这个矩阵可以消解为下面这个矩阵(为便于观察,对实数取整):
这个矩阵除了 外其他 ,所以方程组无穷解
根据上面的例子可以看出,方程的顺序会影响解的判断。那如何在不改变变量位置的情况下能正确地对防方程组进行判断呢?
我们对高斯消元法稍作改进
在消解时,把坡度一致的倒三角改成坡度不一致的倒三角。具体做法是:在消解第 行时,若 行的第 列全为零,常规做法是枚举第 行消解 列,此时我们改变策略,我们保持消解第 行,但是消解第 列,若此时任找不到非 主元,就在第 行 列寻找非零主元,直到找到非零主元为止
e.g. 线性方程组
题目描述
已知 元一次方程组
若有唯一解,求解;若无解,输出 ;若有无穷解,输出
I/O
Input: 第一行一个正整数 表示方程数
接下来 行,每行 个整数,表示
Output: 若无解,输出 ;若有无穷解,输出
若有唯一解,按如下格式输出(保留两位有效数字)
x1=1.00
x2=0.00
...
xn=8.21
范围
解法
#include<bits/stdc++.h>
using namespace std;
#define GO(u,v,i) for(register int i=u;i<=v;i++)
template<class t>inline t fr(){
register t num=0,dis=1;
register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
return num*dis;
}
template<class t>inline void fw(t num){
if(num>9)fw(num/10);
putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
if(num<0)num=-num,putchar('-');
fw(num);putchar(ch);
}
typedef double lf;
const int maxn=100+12;
const lf eps=1e-6;
lf a[maxn][maxn];
int n;
//推荐阅读顺序:main()->gauss()->prnt()
inline void prnt(lf a[][maxn],int base,int n){
if(base<=n){
GO(base,n,i){
if(fabs(a[i][n+1])>eps){
fw(-1,'\n');
return;
}
}
fw(0,'\n');return;
}
GO(1,n,i){
if(fabs(a[i][n+1])<eps)a[i][n+1]=0;
printf("x%d=%.2lf\n",i,a[i][n+1]);
}
}
inline void gauss(lf a[][maxn],int n){
int base=1,maxi;//枚举作为基元的行
GO(1,n,i){
maxi=base;
GO(base+1,n,j)if(fabs(a[j][i])>fabs(a[maxi][i]))maxi=j;
//若a[maxi][i]=0表示base~n行的第i列全为零,此时继续在base行消解
if(fabs(a[maxi][i])<eps)continue;
if(base^maxi)GO(i,n+1,j)swap(a[base][j],a[maxi][j]);
lf tmp=a[base][i];
GO(i,n+1,j){
a[base][j]/=tmp;
}
GO(1,n,j){
if(j==base)continue;
tmp=a[j][i];
GO(i,n+1,k)a[j][k]-=tmp*a[base][k];
}
base++;
}
prnt(a,base,n);
}
signed main(){
n=fr<int>();
GO(1,n,i)GO(1,n+1,j)scanf("%lf",&a[i][j]);
gauss(a,n);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】