[模板] 高斯消元

原理:

每次选取一行的第 \(i\) 个未知数作为主元,把其他行的这个未知数的系数全部削成 \(0\),最后这些式子肯定每个包含一个未知数,这个未知数就是这一行的主元。

注意事项:

  • 每次选取系数最大的并且没选过的作为主元进行交换顺序。\(e.g.\)选取第 \(i\) 个未知数为主元就换到第 \(i\) 行。

  • 控制精度来判断无解情况:

\(1.\) 小于精度就是 \(0\)

\(2.\) 消完元后系数是 \(0\) 而常数不是 \(0\) ,说明无解。

\(3.\) 系数和常数都是 \(0\),说明不止一组解。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=100; 
const double eps=1e-8;
int n;
bool vis[maxn];
double a[maxn][maxn];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n+1;j++)scanf("%lf",&a[i][j]);
	double tmpmax;
	int maxr;
	for(int i=1;i<=n;i++){
		tmpmax=0;maxr=i;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&abs(a[j][i])>tmpmax){
				tmpmax=abs(a[j][i]);maxr=j;
			}
		}
		if(tmpmax<eps)continue;
		if(maxr!=i)swap(a[i],a[maxr]);
		vis[i]=true;
		//消元
		for(int j=1;j<=n;j++){
			if(i!=j){//其他行 
				double t=a[j][i]/a[i][i];
				for(int k=i;k<=n+1;k++)a[j][k]-=t*a[i][k];
			}
		} 
	}
	bool fl=false;
	for(int i=1;i<=n;i++)
		if(abs(a[i][i])<=eps){
			fl=true;
			if(abs(a[i][n+1])>eps){
				puts("-1");return 0;
			}
		}
	if(fl){puts("0");return 0;}
	for(int i=1;i<=n;i++){
		printf("x%d=%.2f\n",i,a[i][n+1]/a[i][i]);
	}
	return 0;
}

Updated on 2021-6-03

以下为更新部分

补充一个细节(写异或高斯时发现的)

注意看下面一行代码:

if(fabs(a[j][i])<eps)continue;

在找到主元后进行消元的过程中,这行显然是不必要的,但是在异或矩阵里会出现问题。

开关问题

对异或矩阵进行消元,与普通实数消元可以说仅有一处不同:

当枚举到每一行进行异或消元时,当且仅当这一行的当前主元存在才可以消元。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	char ch=getchar();bool fl=false;T x=0;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
const int maxn = 35;
int a[maxn][maxn],T,n;
#define read() read<int>()
void solve(){
	int r=1;
	for(int i=1;i<=n;i++){//主元
		int maxr=r;
		for(int j=r+1;j<=n;j++)if(a[j][i])maxr=j;
		if(!a[maxr][i])continue;
		//for(int j=i;j<=n+1;j++)swap(a[maxr][j],a[r][j]);
		swap(a[maxr],a[r]);
		for(int j=r+1;j<=n;j++)//枚举行
			for(int k=n+1;k>=i;k--)
				if(a[j][i])a[j][k]^=a[r][k];//存在才可以消
		r++;
	}
	int ans=1;
	if(r<n+1)
	for(int i=r;i<=n;i++){
		if(a[i][n+1]){
			puts("Oh,it's impossible~!!");return ;
		}
		ans*=2;
	}
	printf("%d\n",ans);return ;
}
int main(){
	T = read();
	while(T--){
		memset(a,0,sizeof a);
		n=read();
		for(int i=1;i<=n;i++)a[i][n+1]=read();
		for(int i=1;i<=n;i++){
			int x=read();
			a[i][n+1]^=x;a[i][i]=1;
		}
		int x,y;
		while(scanf("%d%d",&x,&y) && (x||y)){
			a[y][x]=1;
		}
		solve();
	}
}

同时,为了便于统计答案,可以在消元的过程中记录一个 \(r\) 变量来看当前消元到了多少行。

posted @ 2021-08-12 16:36  ¶凉笙  阅读(24)  评论(0)    收藏  举报