前言
为什么要写这篇文章?因为icpc济南J题翻车了,不会写行列式,手里没板子,现场抄书手写,过不了实属正常,所以必须要整理一下高斯消元了
高斯消元法
{ a 1 , 1 x 1 + a 1 , 2 x 2 + . . . + a 1 , n x n = b 1 a 2 , 1 x 1 + a 2 , 2 x 2 + . . . + a 2 , n x n = b 2 . . . a n , 1 x 1 + a n , 2 x 2 + . . . + a n , n x n = b n \begin{cases} {a_{1,1}x_1+a_{1,2}x_2+...+a_{1,n}x_n= b_1} \\ {a_{2,1}x_1+a_{2,2}x_2+...+a_{2,n}x_n= b_2} \\{...} \\{a_{n,1}x_1+a_{n,2}x_2+...+a_{n,n}x_n= b_n} \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧a1,1x1+a1,2x2+...+a1,nxn=b1a2,1x1+a2,2x2+...+a2,nxn=b2...an,1x1+an,2x2+...+an,nxn=bn
求解上述方程
- 这就是我们在线性代数的前几节课讲的东西,计算过程如下
1.构造增广矩阵如下 [ a 1 , 1 a 1 , 2 a 1 , 3 . . . a 1 , n b 1 a 2 , 1 a 2 , 2 a 2 , 3 . . . a 2 , n b 2 . . . a n , 1 a n , 2 a n , 3 . . . a n , n b n ] \begin{bmatrix} a_{1,1} & a_{1,2} &a_ {1,3} & ... & a_{1,n} & b_1\\ a_{2,1} & a_{2,2} &a_ {2,3} & ... & a_{2,n} & b_2\\ ...\\ a_{n,1} & a_{n,2} &a_ {n,3} & ... & a_{n,n} & b_n\\ \end{bmatrix} ⎣⎢⎢⎡a1,1a2,1...an,1a1,2a2,2an,2a1,3a2,3an,3.........a1,na2,nan,nb1b2bn⎦⎥⎥⎤
2.假设正在处理第 i i i行,选择每一行系数绝对值最大的那一个放到第 i i i行,这样可以减小浮点误差,因为如果一个数非常大的数除以一个非常小的数可能会带来很大的误差,但一个非常小的数除以一个非常大的数带来的误差就不那么明显了
3.用第 i i i行消去 i + 1 i+1 i+1到 n n n行之间的第 i i i位数,这样我们得到的矩阵应该是类似下面的
[ a 1 , 1 ′ a 1 , 2 ′ a 1 , 3 ′ . . . a 1 , n ′ b 1 ′ 0 a 2 , 2 ′ a 2 , 3 ′ . . . a 2 , n ′ b 2 ′ . . . 0 0 0 . . . a n , n ′ b n ′ ] \begin{bmatrix} a_{1,1}' & a_{1,2}' &a_ {1,3}' & ... & a_{1,n}' & b_1'\\ 0 & a_{2,2}' &a_ {2,3}' & ... & a_{2,n}' & b_2'\\ ...\\ 0 & 0 &0 & ... & a_{n,n}' & b_n'\\ \end{bmatrix} ⎣⎢⎢⎡a1,1′0...0a1,2′a2,2′0a1,3′a2,3′0.........a1,n′a2,n′an,n′b1′b2′bn′⎦⎥⎥⎤
4.回代,假设有唯一解,我们根据上面的矩阵已经能够求出 x n x_n xn,然后带到上面的每一个式子中就可以消掉这个位置,然后再根据上一行求出 x n − 1 x_{n-1} xn−1,这样不停地回代就可以求出 x 1 , x 2 , . . . x n x_1,x_2,...x_n x1,x2,...xn
模板
https://www.luogu.com.cn/problem/P3389
此题数据很水,可以测一测有唯一解的情况写的对不对,这个题因为保证不会无解,所以直接判断 a [ n − 1 ] [ n − 1 ] a[n-1][n-1] a[n−1][n−1]是不是为0即可
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-10;
int dcmp(double x){
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<vector<double> > a(n, vector<double> (n + 1));
for(int i=0;i<n;i++){
for(int j=0;j<=n;j++){
cin >> a[i][j];
}
}
function<void()> Gauss = [&](){
for(int i=0;i<n;i++){
int r = i;
for(int j=i+1;j<n;j++){
if(dcmp(fabs(a[j][i]) - fabs(a[r][i])) > 0){
r = j;
}
}
if(r != i) swap(a[i], a[r]);
for(int j=n;j>=i;j--){
for(int k=i+1;k<n;k++){
a[k][j] -= a[k][i] / a[i][i] * a[i][j];
}//刘汝佳白书
}
}
for(int i=n-1;i>=0;i--){
for(int j=i+1;j<n;j++){
a[i][n] -= a[j][n] * a[i][j];
}
a[i][n] /= a[i][i];
}
};
Gauss();
if(dcmp(a[n - 1][n - 1]) == 0) cout << "No Solution";
else{
for(int i=0;i<n;i++){
cout << fixed << setprecision(2) << a[i][n] << '\n';
}
}
return 0;
}
真模板题
https://www.luogu.com.cn/problem/P2455
- 这个题数据比较强,过了此题可以当做模板来用了,但是这个题会出现精度问题,因为小数点后只保留两位,所以精度不好控制,但是没卡精度
- 考虑一下什么时候有无穷多个解,什么时候无解,我们在高斯消元之后,得到的应该是一个上三角矩阵,无解就是出现了0=非零的情况,无穷多解就是出现0=0的情况,那么我们对上面的模板进行改动,先不计算答案,就利用上三角矩阵来看有没有解,如果增广矩阵某一行都是0,那么无穷多解,如果某一行只有1个0,并且这个0还不在最右侧,那么就无解,先输出无解即可
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-10;
int dcmp(double x){
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<vector<double> > a(n, vector<double> (n + 1));
for(int i=0;i<n;i++){
for(int j=0;j<=n;j++){
cin >> a[i][j];
}
}
function<void()> Gauss = [&](){
for(int i=0;i<n;i++){
int r = i;
for(int j=i+1;j<n;j++){
if(dcmp(fabs(a[j][i]) - fabs(a[r][i])) > 0){
r = j;
}
}
if(r != i) swap(a[i], a[r]);
if(dcmp(a[i][i]) == 0) continue;
for(int j=0;j<n;j++){
//全从0开始,最后消得只剩下主对角线
if(i == j) continue;
double f = a[j][i] / a[i][i];//使用中间变量的写法
//可能带来误差
for(int k=0;k<=n;k++){
a[j][k] -= f * a[i][k];
}
}
}
};
Gauss();
bool no = false;
bool many = false;
for(int i=0;i<n;i++){
int num = 0;
for(int j=0;j<=n;j++){
if(dcmp(a[i][j]) == 0) num += 1;
}
if(num == n + 1) many = true;
if(num == n && dcmp(a[i][n]) != 0) no = true;
}
if(no) cout << -1 << '\n';
else if(many) cout << 0 << '\n';
else{
for(int i=n-1;i>=0;i--){
for(int j=i+1;j<n;j++){
a[i][n] -= a[j][n] * a[i][j];
}
a[i][n] /= a[i][i];
}
for(int i=0;i<n;i++){
if(fabs(a[i][n]) < 5e-3) a[i][n] = 0;
cout << 'x' << i + 1 << '=' << fixed << setprecision(2) << a[i][n] << '\n';
}
}
return 0;
}
- 用上面的模板算出来的 a a a矩阵是化简到只剩下主对角线元素的矩阵
行列式求值
基本概念参考线性代数各种教材,这里只讨论如何编程实现
- 如果是浮点数,那么使用上面的高斯消元模板把主对角线元素乘起来就是行列式的值
- 如果是整数,还要求取模,我们还是使用类似上述方法,但是我们消行的时候就不能直接除了,这样取整不好办,这里采用了一种类似辗转相除的方式,利用 j j j行消去第 i i i列下方的所有元素,直到 a [ i ] [ i ] = 0 a[i][i]=0 a[i][i]=0,最后交换回来 a [ i ] [ i ] a[i][i] a[i][i],使得 a [ i ] [ i ] a[i][i] a[i][i]以下都是0,交换行列式的两行之后要设置一个标记记录变号,具体过程见模板
- 可以通过打印中间结果理解这个过程
模板
https://www.luogu.com.cn/problem/P7112
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
ll p;
cin >> n >> p;
vector<vector<ll> > a(n, vector<ll> (n));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin >> a[i][j];
}
}
function<ll()> Gauss = [&](){
ll res = 1;
ll w = 1;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
while(a[i][i]){
ll div = a[j][i] / a[i][i];
for(int k=i;k<n;k++){
a[j][k] = (a[j][k] - div * a[i][k] % p + p) % p;
}
swap(a[i], a[j]);
w = -w;
}
swap(a[i], a[j]);
w = -w;
}
}
for(int i=0;i<n;i++) res = res * a[i][i] % p;
res *= w;
return (res + p) % p;
};
cout << Gauss();
return 0;
}
矩阵求逆
- 计算方法是在右侧设置一个单位矩阵,左侧进行高斯消元直到消至单位矩阵,这个时候右侧得到的即为原矩阵的逆矩阵
模板
https://www.luogu.com.cn/problem/P4783
- 这道题需要使用整数的高斯消元,同时兼备取模操作,作为模板比较合适
- 这题我的模板开 O 2 O2 O2才能过,不知道带这种模板赛场上会不会炸
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-10;
typedef long long ll;
const ll MOD = 1e9 + 7;
ll fastpow(ll base, ll power, ll mod){
ll ans = 1;
while(power){
if(power & 1) ans = ans * base % mod;
base = base * base % mod;
power >>= 1;
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<vector<ll> > a(n, vector<ll> (n << 1));
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin >> a[i][j];
}
a[i][i + n] = 1;
}
function<bool()> Gauss = [&](){
for(int i=0;i<n;i++){
int r = i;
for(int j=i+1;j<n;j++){
if(abs(a[j][i]) - abs(a[r][i]) > 0){
r = j;
}
}
if(r != i) swap(a[i], a[r]);
if(a[i][i] == 0) return false;
ll res = fastpow(a[i][i], MOD - 2, MOD);
for(int j=0;j<n;j++){
if(i == j) continue;
ll f = a[j][i] * res % MOD;
for(int k=i;k<n*2;k++){
a[j][k] -= f * a[i][k];
a[j][k] = (a[j][k] % MOD + MOD) % MOD;
}
}
for(int j=0;j<2*n;j++){
a[i][j] = a[i][j] * res % MOD;
}
}
return true;
};
if(!Gauss()) cout << "No Solution";
else{
for(int i=0;i<n;i++){
for(int j=n;j<2*n;j++){
cout << a[i][j] << ' ';
}cout << '\n';
}
}
return 0;
}