Loading [MathJax]/jax/output/CommonHTML/jax.js

LG3389 【模板】高斯消元法

题意

题目描述

给定一个线性方程组,对其求解

输入输出格式

输入格式:

第一行,一个正整数n
第二至n+1行,每行n+1个整数,为a1,a2anb,代表一组方程。

输出格式:

共n行,每行一个数,第i行为xi(保留2位小数)

如果不存在唯一解,在第一行输出"No Solution".

输入输出样例

输入样例#1:

3
1 3 4 5
1 4 7 3
9 3 2 2

输出样例#1:

-0.97
5.18
-2.39

说明

1n100,|ai|104,|b|104

分析

参照_皎月半洒花的题解。

emmmm这个消元方式其实严格来说是可行性算法……而不是优化性算法……不过话说由于我太蒟蒻了,所以并不知道什么更优的算法(#滑稽)

嗯,其实这个算法是O(n3)的算法,需要一些矩阵及行列式的知识……那么由本蒟蒻来记录一下这个算法吧!

那么假设有一个线性方程组是长这样的:

{3x+2y+z=105x+y+6z=252x+3y+4z=20}

emmm这就是一个很简单的三元一次方程,让我们想想常规方法该怎么做(先不谈code)

初中老师说过:我们可以加减消元或者代入消元,但是我们需要在程序里实现的时候,需要一种有规律可循的算法。所以我们选择加减消元,但用代入消元回带。

整体思路就是我们可以先在某一个式子里,用这个式子的x消去其他式子里的x,然后在剩下的两个式子里再选择一个式子里的y,用这个y消去最后剩下的式子里的y。那么现在最后一个方程里就只有一个未知数z了。倘若z的系数是1,那么我们就可以直接得出答案来了(别觉得这句话是废话)。

比如刚才这个方程,我们用第二个式子去消1、3式里的$x:

{0×x+75y+(135z)=55x+y+6z=250×x+135y+85z=10}

整理之后再用第三个式子里的y消去第一个式子里的y(注意,由于第二个式子作为消元用式,所以接下来的运算不再考虑二式):

{0×y+(22565z)=13513135y+85z=10}

那么我们发现在1式中只剩下一个未知数了,那么就可解得:
z=3
带回三式里解出
y=2
再将xy带回最早被消掉的二式里,解得
x=1
好像这个方法再数学逻辑上讲是特别麻烦的,但是却是一个通用性强的方法qwq
那么放在程序实现上来讲,我们可以先用一个n \times (n+1)n×(n+1)的矩阵来记录每一个式子的系数以及结果。譬如上式就可以用矩阵表示成这样:

{321|10516|25234|20}

左边记录系数,右边记录每个式子的结果。

那么首先我们需要用第一列中(所有的x中)系数最大的来消其他两个式子。而这里为了方便起见,我们将这个选中的系数置为1,方便上例中地不断带回原式的操作(这样在回带的时候就可以不考虑原本的系数了)。

由于最多也只能用double型存储,所以必然会有精度误差。但如果我们每次都选用最大系数的来消掉其他系数,就可以最大程度地来减小误差。以下是一种不严谨地、适合意会的证明(选读):

假设我们现在在处理第nn个未知数,此时在众多的未知数nn中,他们的系数分别是k1k2k3k4km,那么考虑,在选完ki之后,下面我们要进行的是把ki消成1。那么此时对于第ii行的其他的系数以及结果我们都要除以ki

之后呢?之后我们要进行的操作是用这个式子来消掉其他式子里的该未知数啊qwq。如果要这么操作肯定会让其他式子别的未知数的系数,减去当前式子的别的未知数的系数乘上某个值(事实上假设选择含ki的式子,则对于每个式子j而言,每个系数减去当前系数的倍数,这个倍数应该为kj那么这样看来,对于当次用来消元的式子的每个系数qi1qi2qi3qi4qiw (假设当前元的系数是qi1而言,对于每一个其他式子的该项系数qjw,都需要让qjw变成

qjwqj1qi1×qiw

那么我们观察这个式子,qi1越大,qj1qi1期望越小,那么我们考虑,这个值越小,我们就约可以把它看作一个“基本单位”。从而我们就使得减出来的值失精程度越低,最后即可保证数据是从期望上来讲最精确。

嗯,讲的很麻烦,大家挑重点看吧(或者只看最后一个自然段)

在置为1之后,我们需要来用这个式子去消其他的式子(别忘了每个式子的结果也要消)。那么在最后,我们只需要将这个矩阵的最右下角(也就是最后一个元的实际值)不断回带即可。

时间复杂度O(n3)

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;
rg char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') w=-1;
ch=getchar();
}
while(isdigit(ch))
data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x){
return x=read<T>();
}
typedef long long ll;
co double eps=1e-7;
double map[111][111],ans[111];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
int n=read<int>();
for(int i=1;i<=n;++i)
for(int j=1;j<=n+1;++j) read(map[i][j]);
for(int i=1;i<=n;++i){
int r=i;
for(int j=i+1;j<=n;++j)
if(fabs(map[r][i])<fabs(map[j][i])) r=j;
if(fabs(map[r][i])<eps){
puts("No Solution");
return 0;
}
if(i!=r) std::swap(map[i],map[r]);
double div=map[i][i];
for(int j=i;j<=n+1;++j) map[i][j]/=div;
for(int j=i+1;j<=n;++j){
div=map[j][i];
for(int k=i;k<=n+1;++k) map[j][k]-=map[i][k]*div;
}
}
ans[n]=map[n][n+1];
for(int i=n-1;i>=1;--i){
ans[i]=map[i][n+1];
for(int j=i+1;j<=n;++j) ans[i]-=(map[i][j]*ans[j]);
}
for(int i=1;i<=n;++i) printf("%.2lf\n",ans[i]);
return 0;
}

posted on   autoint  阅读(311)  评论(0编辑  收藏  举报

编辑推荐:
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
阅读排行:
· 从零开始开发一个 MCP Server!
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档

导航

点击右上角即可分享
微信分享提示