高斯消元法可以求线性方程组。本质上是暴力模拟手算的过程。
手算的过程为第一个与第二个方程抵消,第二个与第三个抵消。得到一个元二次方程组在经过类似步骤得到解。其中运用了三个小学性质
- 两个方程互换解不变。
- 一个方程乘以非零数解不变。
- 一方程加上另一个方程,解不变。
至于高斯消元法,先将 元一次方程写成 的矩阵,可以任意交换它的行和左边 列,也将一行加到另一行,将一行乘上非零数。
高斯消元法将矩阵消成前 列对角线上元素为 其它为 的单位矩阵。第 列为方程的解。
从方程的意义上,一个未知数的系数至少有一个不为 ,那么矩阵上为一列中至少一个元素不为 否则方程无解,所以先判断无解,并将每一列的非零数所在的行移动到对角线上。
for (int i = 1; i <= n; i++) {
int p = i;
while (!a[p][i] && p <= n) p++;
if (p == n + 1) {
puts("No Solution"); return 0;
}
for (int j = 1; j <= n + 1; j++) swap(a[i][j], a[p][j]);
}
因为要让对角线全为
所以对每一行除上一个数,让这一行对角线位的元素为
。这样会出现小数,所以数组的类型是 double
。
double x = a[i][i]; //a[i][i] 会被更新所以用变量保存
for (int j = 1; j <= n + 1; j++) a[i][j] /= x;
再将前 列其它的元素搞成 。 的系数消成了 要好好利用,将其它行的第 个未知数通过 消到 。
#include <bits/stdc++.h>
using namespace std;
const int N = 300, INF = 0x3f3f3f3f;
inline int read() {
int x = 0, f = 0; char ch = 0;
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
double a[N][N];
int main() {
int n = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n + 1; j++)
a[i][j] = read();
for (int i = 1; i <= n; i++) {
int p = i;
while (!a[p][i] && p <= n) p++;
if (p == n + 1) {
puts("No Solution"); return 0;
}
for (int j = 1; j <= n + 1; j++) swap(a[i][j], a[p][j]);
double x = a[i][i];
for (int j = 1; j <= n + 1; j++) a[i][j] /= x;
for (int j = 1; j <= n; j++)
if (i != j) {
x = a[j][i];
for (int k = 1; k <= n + 1; k++) a[j][k] -= a[i][k] * x;
}
}
for (int i = 1; i <= n; i++) printf("%.2lf\n", a[i][n + 1]);
return 0;
}
可以发现高斯消元法有点暴力的成分,时间复杂度为 。
模板题 Gym 100644H 配平化学方程式 CF113D Museum。