高斯消元

最近遇到了几道高斯消元的题,虽然学了线性代数知道求解方法,但无法通过编程实现,相信还有少部分人也遇到了这种问题,所以写下这篇博文

  1. 什么是高斯消元

数学上,高斯消元法(或译:高斯消去法),是线性代数规划中的一个算法,可用来为线性方程组求解。但其算法十分复杂,不常用于加减消元法,求出矩阵的秩,以及求出可逆方阵的逆矩阵。不过,如果有过百万条等式时,这个算法会十分省时。一些极大的方程组通常会用迭代法以及花式消元来解决。当用于一个矩阵时,高斯消元法会产生出一个“行梯阵式”。高斯消元法可以用在电脑中来解决数千条等式及未知数。亦有一些方法特地用来解决一些有特别排列的系数的方程组。

简单的说,通过高斯消元我们可以方便的解决多元一次方程组。

2、思路
其实就是通过编程来模拟现实生活中解决线性代数问题。依次让某一列从第几行开始变换成0,得到上三角形矩阵,就可以判断出答案是无解、无穷多解还是有唯一解了。

大家可以看我待会前两题的代码理解一下,第一题代码有注释,最好自己动手模拟一下。
实在不懂的话,这里建议y总的acwing基础课套餐来一个或者去学一下线性代数。
不难,用心学就会了。

废话不多说,直接上模板和题目

3、题目
第一题: 题目来源:acwing里面的–高斯消元解线性方程组
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=105;
const double eps=1e-6;
int n;
double a[N][N];

int guass(){
	int c,r;	//c为当前考虑的列数,即当前将第c列变成系数为1的行列式。 r为在考虑当前第c列的情况下,初始行数 
	for(c=0,r=0;c<n;c++){
		int t=r;	 //t为在考虑当前第c列的情况下,系数最大的行数
		for(int i=r;i<n;i++){
			if(fabs(a[i][c])>fabs(a[t][c])){  //在当前考虑第c列的情况下,如果第i行第c列的数>第t行第c列的数 ,则t=i,用来记录最大的数是哪一行 
				t=i;
			}
		}
		
		if(fabs(a[t][c])<eps) continue;		//如果在考虑当前第c列的情况下,最大的那一行的第c列的系数为0,那么继续,直接结束该次循环,去之星下一列的重复操作 
		
		for(int i=c;i<=n;i++) swap(a[t][i],a[r][i]);//在考虑第c列的情况下,将初始行数r的所有数与最大行数t的所有数进行交换 
		for(int i=n;i>=c;i--) a[r][i]/=a[r][c];//将第r行的第c列的数变成1,该行所有数都要进行初等行列式变换 
		for(int i=r+1;i<n;i++){ //从第r+1行起到第n行 的所有第c列的系数全部通过初等行列式变换成0 
			if(fabs(a[i][c])>eps){
				for(int j=n;j>=c;j--){
					a[i][j]-=a[r][j]*a[i][c];
				}
			}
		}
		r++;
	}
	   
	if(r<n){
		for(int i=r;i<n;i++){
			if(fabs(a[i][n])>eps) return 2;//无解 
		}
		return 1;	//无穷多组解
	}
	//唯一解 
	for(int i=n-1;i>=0;i--){	//反向从第n行到第1行求Xn的值 
		for(int j=i+1;j<n;j++){
			a[i][n]-=a[i][j]*a[j][n]; 
		}
	}
	
	return 0;
}

int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		for(int j=0;j<n+1;j++){
			scanf("%lf",&a[i][j]);
		}
	}
	
	int t=guass();
	if(t==0) for(int i=0;i<n;i++) printf("%.2lf\n",a[i][n]); //有唯一解并输出唯一解 
	else if(t==1) puts("Infinite group solutions");		//有无穷多组解 
	else puts("No solution");    //无解
	
	return 0;
}

如果大家看懂了第一题的做法,可以试着解决一下第二题,是第一题的简单变式。

第二题:题目来源:acwing里面的:高斯消元解异或方程组
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n;
int a[N][N];

int gauss(){
	int r,c;
	for(r=c=0;c<n;c++){
		int t=r;
		for(int i=r;i<n;i++){
			if(a[i][c]){
				t=i;break;
			}
		}
		if(!a[t][c]) continue;
		
		for(int i=c;i<=n;i++) swap(a[t][i],a[r][i]);
		for(int i=r+1;i<n;i++){
			if(a[i][c]){
				for(int j=c;j<=n;j++){
					a[i][j]^=a[t][j];
				}
			}
		}
		r++;
	}
	if(r<n){
		for(int i=r;i<n;i++){
			if(!a[i][n]) return 2;
		}
		return 1;
	}
	
	for(int i=n-1;i>=0;i--){
		for(int j=i+1;j<n;j++){
			a[i][n]^=a[i][j]*a[j][n];
		}
	}
	
	return 0;
}

int main(){
	cin>>n;
	for(int i=0;i<n;i++){
		for(int j=0;j<=n;j++){
			cin>>a[i][j];
		}
	}
	int res=gauss();
	if(res==0){
		for(int i=0;i<n;i++) cout<<a[i][n]<<"\n";
	}
	else if(res==1) cout<<"Infinite group solutions"<<"\n";
	else cout<<"No solution"<<"\n";
}

**现在大家应该都对高斯消元有了一定的理解了。下面直接上两大题给大家练练手,第一时间没有解出不用气馁,这里一道是2021年ICPC济南的A题,一道是2020-2021 ACM-ICPC Asia Seoul Regional Contest ----J题 **

济南A题—Matrix Equation

在这里插入图片描述
在这里插入图片描述
这题说白了就是算每一列的自由元有多少个.假设第i列的自由元有Xi个,则每一列对答案的贡献就是2^(Xi),最终的答案就是每一列对答案的贡献的乘积,数据有点大,在算每一列对自由元的贡献时记得加上快速幂节省时间。 也可以计算先计算出所有的自由元个数num,答案就是2^(num),记得答案要取模。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int N=205;
int n;
ll a[N][N],b[N][N],d[N][N];

ll ksm(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res%mod;
}

ll gauss(){
    int r,c,num=0;
    for(r=c=1;c<=n;c++){
        int t=r;
        for(int i=r+1;i<=n;i++){
            if(d[i][c]){
                t=i;break;
            }
        }
        if(t!=r) for(int i=c;i<=n;i++) swap(d[t][i],d[r][i]);
        if(!d[r][c]){
            num++;
            continue;
        }

        for(int i=r+1;i<=n;i++){
            if(d[i][c]){
                for(int j=c;j<=n;j++){
                    d[i][j]^=d[r][j];
                }
            }
        }
        r++;
    }
    return num;
}

int main(){
    ios::sync_with_stdio(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>b[i][j];
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=n;k++){
                d[j][k]=a[j][k];
            }
            d[j][j]^=b[j][i];
        }
        ans+=gauss();
    }
    ll res=1;
    res=ksm(2,ans)%mod;
    cout<<res<<endl;
    return 0;
}

2020-2021 ACM-ICPC Asia Seoul Regional Contest J-Switches

在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210430203523922.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cnJ5bGp4,size_16,color_FFFFFF,t_70#pic_center
每盏灯可能收多个开关控制,当有奇数个开关打开时,灯亮;否则灯灭。
问是否对某一盏灯而言,有一种开关情况使其亮起,而其他灯都熄灭。这里是类似第二题的解异或方程组

朴素的写法

#include<bits/stdc++.h>
using namespace std;
const int N=505;
int n;
int a[N][N*2],b[N][N],c[N][N*2],ch[N][N];

void init(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			a[i][j]=c[i][j];
		}
	}
}

int gauss(){
	int c,r;
	for(r=c=1;c<=n;c++){
		int t=r;
		for(int i=r;i<=n;i++){
			if(a[i][c]){
				t=i;break;
			}
		}
		if(!a[t][c]) continue;
		for(int i=c;i<=n*2;i++) swap(a[t][i],a[r][i]);
		for(int i=1;i<=n;i++){
			if(a[i][c]&&i!=r){
				for(int j=c;j<=n*2;j++){
					a[i][j]^=a[r][j];
				}
			}
		} 
		r++;
	}
	if(r<=n) return 1;
	else return 0;
}

int main(){
	scanf("%d",&n);
	for(int j=1;j<=n;j++){
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i][j]);
			c[i][j]=a[i][j];
			if(i==j) ch[i][j]=1;
		}
		a[j][n+j]=1;  //灯亮 
	}
	
	int res=gauss();
	if(res==1) puts("-1");
	else{
		for(int j=n+1;j<=n*2;j++){
			for(int i=1;i<=n;i++){
				if(a[i][j]) printf("%d ",i);
			}
			printf("\n");
		}
	}
	return 0;
}

bitset优化写法

#include<bits/stdc++.h>
#include<bitset>
using namespace std;
const int N=505;
bitset<N*2>a[N];
bitset<N>b,inv_a[N],ans;
int n;

bool gauss(){
	int c;
	for(int c=1;c<=n;c++){
		int t=c;
		for(int i=c;i<=n;i++){
			if(a[i][c]){
				t=i;
				break;
			}
		}
		if(a[t][c]==0) return false;
		swap(a[t],a[c]);
		for(int i=1;i<=n;i++){
			if(a[i][c]&&i!=c){
				a[i]^=a[c];
			}
		}
	}
	return true;
}

void mul(bitset<N>& a,bitset<N> b[],bitset<N>& c){
	for(int i=1;i<=n;i++){
		a[i]=(b[i]&c).count()%2;
	}
}

int main(){
	scanf("%d",&n);
	for(int j=1;j<=n;j++){
		for(int i=1;i<=n;i++){
			int x;
			scanf("%d",&x);
			if(x) a[i][j]=1;
		}
		a[j][j+n]=1;
	}
	if(gauss()==0) puts("-1");
	else{
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				inv_a[i][j]=a[i][j+n];
			}
		}
		for(int i=1;i<=n;i++){
			b.reset();
			b[i]=1;
			ans.reset();
			mul(ans,inv_a,b);
			for(int i=1;i<=n;i++){
				if(ans[i]) printf("%d ",i);
			}
			printf("\n");
		}
	}
}

总结:其实写高斯消元的题挺有意思的,主要考察大家的分析问题能力和码力。大家有高斯消元的好题可以留言或私信我,让我也来写写

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值