[笔记]高斯消元

news/2025/10/20 21:57:21/文章来源:https://www.cnblogs.com/Sinktank/p/19146575

高斯消元法是求解线性方程组的经典算法。

内容

求解如下的线性方程组(P3389 【模板】高斯消元法):

\[\begin{cases} a_{1,1}x_1+a_{1,2}x_2+\dots+a_{1,n}x_n=b_1\\ a_{2,1}x_1+a_{2,2}x_2+\dots+a_{2,n}x_n=b_2\\ \dots\\ a_{n,1}x_1+a_{n,2}x_2+\dots+a_{n,n}x_n=b_n \end{cases} \]

我们考虑将所有变量丢到一个 \(n\times (n+1)\) 的矩阵中,其中最后一列存储常数项 \(b_1,\dots,b_n\)

\[\begin{bmatrix} a_{1,1}&a_{1,2}&\cdots&a_{1,n}&b_1\\ a_{2,1}&a_{2,2}&\cdots&a_{2,n}&b_2\\ \vdots&\vdots&\ddots&\vdots&\vdots\\ a_{n,1}&a_{n,2}&\cdots&a_{n,n}&b_n \end{bmatrix} \]

我们称这个矩阵为增广矩阵

高斯消元的理论依据:

  1. 交换律:将两行交换,方程的解不变。
  2. 加法律:将某行加上另外一行,方程的解不变。
  3. 乘法律:将某行乘一个非零常数,方程的解不变。
  4. 乘加律:将某行乘上一个常数,加到另一行上,方程的解不变。可由 \(2,3\) 推出。

我们的目标是利用这些性质,对增广矩阵做一些变换,使它变成下面的形态,一个下三角矩阵(Step 1):

\[\begin{bmatrix} a'_{1,1}&a'_{1,2}&a'_{1,3}&\cdots&a'_{1,n}&b'_1\\ 0&a'_{2,2}&a'_{2,3}&\cdots&a'_{2,n}&b'_2\\ 0&0&a'_{3,3}&\cdots&a'_{3,n}&b'_3\\ \vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\ 0&0&0&\cdots&a'_{n,n}&b'_n \end{bmatrix} \]

最后再变成这样(Step 2):

\[\begin{bmatrix} 1&0&0&\cdots&0&b''_1\\ 0&1&0&\cdots&0&b''_2\\ 0&0&1&\cdots&0&b''_3\\ \vdots&\vdots&\vdots&\ddots&\vdots&\vdots\\ 0&0&0&\cdots&1&b''_n \end{bmatrix} \]

Step 1

我们依次遍历 \(i=1,2,\dots,n\),对于第 \(i\) 行,我们选定 \(a_{i,i}\) 为主元(\(a_{i,i}\ne 0\))用乘加律将 \(i+1,\dots,n\) 这些行的第 \(i\) 列全部消成 \(0\)

这样进行下去就能获得下三角矩阵了。

需要注意,若 \(a_{i,i}=0\),则上面的步骤无法进行。需要向下找一个 \(j\) 使得 \(a_{j,i}\ne 0\),然后利用交换律将 \(i,j\) 行交换,再继续进行。

若无法找到这样的 \(j\),则说明无解,或有无穷多解。

Step 2

从最后一行开始,逐个回带即可。相当于用第 \(n\) 行将第 \(n-1\) 行的第 \(n\) 列消成 \(0\)……以此类推。

  • 若回带过程中遇到 \(0x=k(k\ne 0)\) 则说明无解。
  • 在有解得情况下,若遇到 \(0x=0\) 则说明有无穷多解。
  • 否则有唯一解。

通常高斯消元会用 double 存储结果,为了减小精度误差,两个值差距在一定范围内(如 \(10^{-7}\))即视作相等。

另外为了减小误差,通常选取 \(a_{j,i}\) 最大的 \(j\) 和第 \(i\) 行进行交换(这样做的可行性在这里有更详细的说明)。

代码实现的 Step 1,先将第 \(i\) 行整体乘法,使得 \(a_{i,i}=1\) 再进行其他操作,这样乘加时就不需要考虑 \(a_{i,i}\) 的取值,且 Step 2 也不需要再考虑系数问题了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-7;
const int N=105;
double a[N][N],ans[N];
int n;
signed main(){cin>>n;for(int i=1;i<=n;i++) for(int j=1;j<=n+1;j++) cin>>a[i][j];for(int i=1;i<=n;i++){//枚举主元序号(即列的编号)int r=i;for(int j=i+1;j<=n;j++){if(abs(a[j][i])>abs(a[r][i])) r=j;}if(abs(a[r][i])<eps) cout<<"No Solution\n",exit(0);//找不到非 0 的行,无解或无穷多解if(i^r) swap(a[i],a[r]);double div=a[i][i];for(int j=i;j<=n+1;j++) a[i][j]/=div;//使 a[i][i]=1,更方便一些for(int j=i+1;j<=n;j++){div=a[j][i];for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*div;}}ans[n]=a[n][n+1];//对角线已经是 1for(int i=n-1;i;i--){ans[i]=a[i][n+1];for(int j=i+1;j<=n;j++) ans[i]-=a[i][j]*ans[j];}for(int i=1;i<=n;i++)cout<<fixed<<setprecision(2)<<ans[i]<<"\n";return 0;
}

高斯-约旦消元

高斯-约旦消元相比高斯消元,通过对 Step 1 进行少量修改,可以直接使原矩阵变换成第二个矩阵,从而省掉 Step 2。

具体地,对于第 \(i\) 行,除了将 \(i+1,\dots,n\) 行的第 \(i\) 列消成 \(0\),我们把 \(1,\dots,i-1\) 行的第 \(i\) 列也消成 \(0\)。这样我们直接就得出了第二个矩阵,直接访问 \(f_{i,n+1}\) 即可获得 \(x_i\) 的值了。

码量小了不少。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-7;
const int N=105;
double a[N][N];
int n;
signed main(){cin>>n;for(int i=1;i<=n;i++) for(int j=1;j<=n+1;j++) cin>>a[i][j];for(int i=1;i<=n;i++){//枚举主元序号(即列的编号)int r=i;for(int j=i+1;j<=n;j++){if(abs(a[j][i])>abs(a[r][i])) r=j;}if(abs(a[r][i])<eps) cout<<"No Solution\n",exit(0);if(i^r) swap(a[i],a[r]);double div=a[i][i];for(int j=i;j<=n+1;j++) a[i][j]/=div;for(int j=1;j<=n;j++){if(j==i) continue;div=a[j][i];for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*div;}}for(int i=1;i<=n;i++)cout<<fixed<<setprecision(2)<<a[i][n+1]<<"\n";return 0;
}

P2455 [SDOI2006] 线性方程组

板子题,和刚才的 P3389 区别仅在与无解和无穷多解需要不同的输出。

然而我们刚才的代码会 WA 90pts。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-7;
const int N=55;
double a[N][N];
int n;
bool infi; 
signed main(){cin>>n;for(int i=1;i<=n;i++) for(int j=1;j<=n+1;j++) cin>>a[i][j];for(int i=1;i<=n;i++){//枚举主元序号(即列的编号)int r=i;for(int j=i+1;j<=n;j++){if(abs(a[j][i])>abs(a[r][i])) r=j;}if(abs(a[r][i])<eps) continue;//无解或无穷多解 if(i^r) swap(a[i],a[r]);double div=a[i][i];for(int j=i;j<=n+1;j++) a[i][j]/=div;for(int j=1;j<=n;j++){if(j==i) continue;div=a[j][i];for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*div;}}for(int i=1;i<=n;i++){if(abs(a[i][i])<eps){if(abs(a[i][n+1])>eps) cout<<"-1\n",exit(0);infi=1;}}if(infi) cout<<"0\n",exit(0);for(int i=1;i<=n;i++)cout<<"x"<<i<<"="<<fixed<<setprecision(2)<<a[i][n+1]<<"\n";return 0;
}

其原因在于,当我们发现第 \(i\) 列不存在非零的元素后,我们会 continue 掉,继续对第 \(i+1\) 行进行处理。

\(i\) 行中只是第 \(i\) 列不存在非零的元素,第 \(i+1\) 列是可能存在非零元素的,但是它被我们 continue 掉了,不会再处理。这就可能导致无穷多解被判成无解。

可以参考这组 Hack。

2
0 2 3
0 0 0

我们的对策也很简单,只需要在找第 \(i\) 列非零元素时,一并将第 \(1,\dots,i-1\) 行也遍历一遍就可以了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-7;
const int N=55;
double a[N][N];
int n;
bool infi; 
signed main(){cin>>n;for(int i=1;i<=n;i++) for(int j=1;j<=n+1;j++) cin>>a[i][j];for(int i=1;i<=n;i++){//枚举主元序号(即列的编号)int r=i;for(int j=1;j<=n;j++){if(abs(a[j][j])>eps&&j<i) continue;//行 j 的在第 j 列已经存在主元,则不可用if(abs(a[j][i])>abs(a[r][i])) r=j;}if(abs(a[r][i])<eps) continue;//无解或无穷多解if(i^r) swap(a[i],a[r]);double div=a[i][i];for(int j=i;j<=n+1;j++) a[i][j]/=div;for(int j=1;j<=n;j++){if(j==i) continue;div=a[j][i];for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*div;}}for(int i=1;i<=n;i++){if(abs(a[i][i])<eps){if(abs(a[i][n+1])>eps) cout<<"-1\n",exit(0);infi=1;}}if(infi) cout<<"0\n",exit(0);for(int i=1;i<=n;i++)cout<<"x"<<i<<"="<<fixed<<setprecision(2)<<a[i][n+1]<<"\n";return 0;
}

另一种方法是将构成阶梯的行都 swap 到上面去,这样每次向下遍历即可保证正确性。更简洁一些,我更中意这个。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-7;
const int N=55;
double a[N][N];
int n,idx=1;
signed main(){cin>>n;for(int i=1;i<=n;i++) for(int j=1;j<=n+1;j++) cin>>a[i][j];for(int i=1;i<=n;i++){//枚举主元序号(即列的编号)int r=idx;for(int j=r+1;j<=n;j++){if(abs(a[j][i])>abs(a[r][i])) r=j;}if(abs(a[r][i])<eps) continue;//无解或无穷多解if(idx^r) swap(a[idx],a[r]);double div=a[idx][i];for(int j=i;j<=n+1;j++) a[idx][j]/=div;for(int j=1;j<=n;j++){if(j==idx) continue;div=a[j][i];for(int k=i;k<=n+1;k++) a[j][k]-=a[idx][k]*div;}idx++;}if(idx<=n){while(idx<=n) if(abs(a[idx++][n+1])>eps) cout<<"-1\n",exit(0);cout<<"0\n";}else{for(int i=1;i<=n;i++)cout<<"x"<<i<<"="<<fixed<<setprecision(2)<<a[i][n+1]<<"\n";}return 0;
}

P10499 开关问题

若按下开关 \(j\) 会改变开关 \(i\) 的状态,则令 \(a_{i,j}=1\),否则为 \(0\)

同时,令 \(x_i=1/0\) 分别为 \(i\) 个开关按下 / 不按下。

则所有约束条件可以用下面的异或方程组来描述:

\[\begin{cases} a_{1,1}x_1\oplus a_{1,2}x_2\oplus \dots\oplus a_{1,n}x_n=s_1\oplus t_1\\ a_{2,1}x_1\oplus a_{2,2}x_2\oplus \dots\oplus a_{2,n}x_n=s_2\oplus t_2\\ \dots\\ a_{n,1}x_1\oplus a_{n,2}x_2\oplus \dots\oplus a_{n,n}x_n=s_n\oplus t_n \end{cases} \]

异或可以理解为模 \(2\) 意义下的加法,所以可以用高斯消元做。

考虑到两个 bitset 可以直接异或,所以写起来会方便很多。

或者利用 \(n\) 很小,将每行的 \(a\) 压缩到一个整数里也行。

时间复杂度为 \(O(\dfrac{n^3}{\omega})\) 或者 \(O(n^2)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=32;
int k,n,a[N],g[N];
inline int gauss(){for(int i=0;i<n;i++){for(int j=i+1;j<n;j++){if(a[j]&(1<<i)){swap(a[i],a[j]),swap(g[i],g[j]);break;}}if(a[i]&(1<<i))for(int j=0;j<n;j++){if((i^j)&&(a[j]&(1<<i))) a[j]^=a[i],g[j]^=g[i];}}int ans=0;for(int i=0;i<n;i++){if(!a[i]){if(g[i]) return -1;else ans++;}}return 1<<ans;
}
signed main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);cin>>k;while(k--){cin>>n;for(int i=0;i<n;i++) cin>>g[i],a[i]=(1<<i);for(int i=0,x;i<n;i++) cin>>x,g[i]^=x;int u,v;while(cin>>u>>v){if(!u) break;u--,v--;a[v]|=(1<<u);}int ans=gauss();if(~ans) cout<<ans<<"\n";else cout<<"Oh,it's impossible~!!\n";}return 0;
}

这就是高斯消元求解异或方程组。

P5027 Barracuda

我们可以枚举不合法的方程,然后判断解的情况即可。

具体来说,必须恰有一种方案有合法解。其中合法解要满足:

  • 所有解均为正整数。
  • 最大值唯一。

时间复杂度 \(O(n^4)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=105;
const double eps=1e-7;
int n,idx,b[N][N],ans;
double a[N][N];
inline int gauss(){idx=1;for(int i=1;i<=n;i++){int r=idx;for(int j=r+1;j<=n;j++){if(abs(a[j][i])>abs(a[r][i])) r=j;}if(abs(a[r][i])<eps) return 0;if(idx^r) swap(a[idx],a[r]);double div=a[idx][i];for(int j=i;j<=n+1;j++) a[idx][j]/=div;for(int j=1;j<=n;j++){if(j==idx) continue;div=a[j][i];for(int k=i;k<=n+1;k++) a[j][k]-=a[idx][k]*div;}idx++;}int mx=0,f=0,p=0;for(int i=1,t;i<=n;i++){t=a[i][n+1]+0.5;if(a[i][n+1]<eps||abs(a[i][n+1]-t)>eps){return 0;}if(t>mx) mx=t,p=i;}for(int i=1;i<=n;i++) if(abs(a[i][n+1]-mx)<eps){if(f) return 0;f=1;}return p;
}
signed main(){cin>>n;for(int i=1,m,x;i<=n+1;i++){cin>>m;while(m--) cin>>x,b[i][x]=1;cin>>x,b[i][n+1]=x;}for(int x=1;x<=n+1;x++){for(int i=1,idx=0;i<=n+1;i++){if(i==x) continue;++idx;for(int j=1;j<=n+1;j++) a[idx][j]=b[i][j];}int t=gauss();if(t){if(ans) cout<<"illegal\n",exit(0);ans=t;}}if(ans) cout<<ans<<"\n";else cout<<"illegal\n";return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/941654.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[SSH] scp:基于 SSH 的安全文件传输

[SSH] scp:基于 SSH 的安全文件传输$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");目录01 简介02 操作2.1 本地发送到远程2.2 从远程下载2.3 主机之间复制…

CSP-S 35

10.2010.20 神秘%你赛,rk1 170 ,你管这叫CSP-S? t1 赛时狂写t1 ,想了半天想出来个神秘做法,时间复杂度不会证但应该是对的,写完本地大阳历1.2s 感觉应该没啥大问题,结果空间炸了,最后2h写的代码和暴力分一样多 …

题解:P11662 [JOI 2025 Final] 方格染色 / Grid Coloring

题目传送门 是一道黄题 这里提供一种 \(O(n\log n)\) 的做法\(\mathscr{PART\ \ ONE}\)我们在手%的时候不难发现(注意力有点也不惊人) 虽然第一列和第一行 不保证有序 但是因为这里的前缀$\ max\ $性质保证了第二列和…

CSP-S 32 多校5

10.1510.15 S-32&&多校5 签到后遗憾离场。 t1 签到题,大力分讨即可。 注意符号。 记得排序(没大阳历时忘排序了) code锵锵 #include <bits/stdc++.h> using namespace std; const int N = 1e6 + 10;…

CSP-S 33

10.1710.17 t1 签到题 注意到约数只有 \(O(\sqrt n)\) 级别,暴力找约数即可。 唐人当然有唐做法啦! 分解质因数+dfs搜约数 反正唐就对了。 code嘻嘻 #include <bits/stdc++.h> #define int long long #define…

CSP-S 29

10.11 只记录了当时认为有意义的题10.11 t3 赛后才看懂题面 \(\ldots\) 妈妈我会推式子!(骗你的其实我不会) 推完式子就过了。 考虑先求出 \(1\) 为根时的答案,之后换根即可。 开始拆贡献ing 对于根结点,由于一开…

10.20每日总结

今天主要的课程有软件设计,软件开发案例分析,大数据技术,物联网工程。完成了好几项作业,满课还是太忙太累了,软考网课开始第三章。

CSP-S 31

10.1410.14 看大家得分跟信心赛似的,就我一个唐诗写了一场暴力 \(\ldots\) 开场t1没切出来就开始慌了,之后就想着多打暴力拿部分分,导致t2得出的性质没有推广,t3,t4暴力都没写出来(这俩得出性质/部分性质后比暴力…

2025网络安全振兴杯wp

振兴杯wp web1 神探狄仁杰在js和源代码,以及关于里面有flag的base64字段 css中关于的源代码中然后解密就行了 web2Darksale 这个是一个原型链污染 我们发现购买的金额可以被改变我们发现改价格后会回显出来我们尝试修…

ES原理、zookeeper、kafka

ES原理、zookeeper、kafkaES高级 ES底层原理 协调节点是 Elasticsearch 中接收客户端请求、将请求转发到相关数据节点、并汇总最终结果返回给客户端的中心路由节点 Cluster State 是 Elasticsearch 集群的元数据大脑,…

CF1606E Arena 题解(动态规划)

考虑设 \(f_{i,j}\) 表示现在存活 \(i\) 个人,血量最大的人为 \(j\)。这么设是因为注意到有没有胜者其实之和血量最大的是谁,以及有多少个血量最大的有关。 边界情况 \(f_{1,i}=0\)。 考虑转移。如果 \(j<i\),则…

服务器CPU市场概况2025

2025年的服务器CPU市场正处于关键转型期。传统的x86架构(以Intel和AMD为代表)依然占据主导,但基于Arm的解决方案正在快速崛起。随着人工智能(AI)、云计算和高性能计算(HPC)的普及,CPU的设计趋势正在朝着能效优…

CSP-S 24

9.21~9.2?9.21 今天开始集训告一段落了,去补文化课一周。 如果写不完回来会补(?) 115=100+0+15 t1 先等会 t2 先等会 t3 9.22:回来补债了 \([\gcd(i,j)=1]=[\gcd(p_i,p_j)=1]\) 这个限制初看好像很难转化,只能猜…

正睿 2025 NOIP20 连测 Day5 做题记录

T1给 \(m\) 个质数,第 \(i\) 个质数 \(p_i\) 出现了 \(n_i\) 次。求一种划分质数的方案,使得第一个集合的和等于第二个集合的乘积。萌萌题,注意到最后相当于是要求 \(p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_k^{\alp…

29-腾讯云COS接入指南与价格说明

腾讯云对象存储(COS)接入流程与价格说明 一、腾讯云COS简介 腾讯云对象存储(Cloud Object Storage,COS)是腾讯云提供的一种存储海量文件的分布式存储服务,具有高扩展性、低成本、高可靠性和高安全性等特点。用户可…

LLM学习记录DAY7

📘今日LLM学习笔记总结 一、大模型解码策略 1.1 自回归解码定义:逐词生成下一个词,基于已生成内容继续生成。 流程:输入词序列 ( u ) 重复:模型输出下一个词的概率分布 ( P ) 采样或选择下一个词 ( u ) 将 ( u )…

CSP-S 23

9.179.17 109=9+100 赛时将剩余时间“明智”的投入t4,结果就是后两题没分。 t1 三维前缀和+二分答案 写了个假的kdt调了2h... 二分显然是好想的。 看值域考虑复杂度: \(256\times 256\times 256\times 8=134217728\) …

Recall

可惜了,这静谧的长夜。 是夕阳,余晖照在我的肩上。 惨白的灯喧哗着。 昏黑,安静的可怕。 滴答,滴答。 滴答,滴答。 昏黑,安静的可怕。 惨白的灯喧哗着。 是夕阳,余晖照在我的肩上。 可惜了,这静谧的长夜。

CSP-S 20

9.119.11 今天是挂分的好日子~~~ 101=0+10+91 t1 追逐游戏 (chase) 倍增+k祖先 算了不想写了....... 第2次没保存然后死机丢失了... 不难理解也不难实现,细节手模即可。 t1 就这样吧,看代码。 code点击查看代码 #…