一、AcWing 883. 高斯消元解线性方程组
【题目描述】
输入一个包含
n
n
n个方程
n
n
n个未知数的线性方程组。
方程组中的系数为实数。
求解这个方程组。
一个包含
m
m
m个方程
n
n
n个未知数的线性方程组示例如下:
{ a 11 x 1 + a 12 x 2 + ⋯ + a 1 n x n = b 1 a 21 x 1 + a 22 x 2 + ⋯ + a 2 n x n = b 2 … a m 1 x 1 + a m 2 x 2 + ⋯ + a m n x n = b m \left\{ \begin{aligned} &a_{11}x_1+a_{12}x_2+\dots +a_{1n}x_n=b_1 \\ &a_{21}x_1+a_{22}x_2+\dots +a_{2n}x_n=b_2 \\ &\dots \\ &a_{m1}x_1+a_{m2}x_2+\dots +a_{mn}x_n=b_m \end{aligned} \right. ⎩ ⎨ ⎧a11x1+a12x2+⋯+a1nxn=b1a21x1+a22x2+⋯+a2nxn=b2…am1x1+am2x2+⋯+amnxn=bm
【输入格式】
第一行包含整数
n
n
n。
接下来
n
n
n行,每行包含
n
+
1
n+1
n+1个实数,表示一个方程的
n
n
n个系数以及等号右侧的常数。
【输出格式】
如果给定线性方程组存在唯一解,则输出共
n
n
n行,其中第
i
i
i行输出第
i
i
i个未知数的解,结果保留两位小数。
如果给定线性方程组存在无数解,则输出Infinite group solutions
。
如果给定线性方程组无解,则输出No solution
。
【数据范围】
1
≤
n
≤
100
1≤n≤100
1≤n≤100
所有输入系数以及常数均保留两位小数,绝对值均不超过
100
100
100。
【输入样例】
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00
【输出样例】
1.00
-2.00
3.00
【分析】
什么是高斯消元法?( O ( n 3 ) O(n^3) O(n3))
- 通过初等行(列)变换把增广矩阵化为阶梯型矩阵并回代得到方程的解
- 适用于求解包含 n n n个方程, n n n个未知数的多元线性方程组
什么是初等行(列)变换?
- 把某一行乘一个非零的数(方程的两边同时乘上一个非零数不改变方程的解)
- 交换某两行(交换两个方程的位置)
- 把某行的若干倍加到另一行上去(把一个方程的若干倍加到另一个方程上去)
高斯消元算法步骤:
- 枚举每一列 c c c
- 找到当前列绝对值最大的一行
- 把这一行换到最上面(未确定阶梯型的行,并不是第一行)
- 将该行的第一个数变成 1 1 1(其余所有的数字依次跟着变化)
- 将下面所有行的当前列的值变成 0 0 0(其余所有的数字依次跟着变化)
如果每一行都进行了如上的操作,说明方程组有唯一解,否则需要对未操作到的方程进行判断,既然是枚举每一列进行操作的,因此未被操作的行除了最右边一列(即 b b b)其余的所有元素均为 0 0 0,因此只需判断 b b b那一列即可:
- 若出现了某一行的 b b b不为 0 0 0,即出现了 0 = C 0=C 0=C( C C C为非零数)的情况,说明无解
- 若剩余行的 b b b全为 0 0 0,即剩下的方程均为 0 = 0 0=0 0=0,是多余方程,说明有无穷多组解
【代码】
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
const double EPS = 1e-6;
double a[N][N];
int n;
int gauss()//返回0表示有唯一解,1表示有无穷多组解,2表示无解
{
int r, c;//分别表示行、列
for (r = 0, c = 0; c < n; c++)
{
//第一步:找出第c列中绝对值最大的那一行
int t = r;
for (int i = r + 1; i < n; i++)
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < EPS) continue;//如果最大的数为0说明所有方程的这一列都为0
//第二步:将这一行换至第一行(未确定方程的第一行),即第r行
for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]);
//第三步:将这一行第c列的数变为1(注意从后往前,最后更新a[r][c])
for (int i = n; i >= c; i--) a[r][i] /= a[r][c];
//第四步:将这一行之后的每一行的第c列的数变成0
for (int i = r + 1; i < n; i++)
if (fabs(a[i][c]) > EPS)
for (int j = n; j >= c; j--)
a[i][j] -= a[i][c] * a[r][j];
r++;//处理完当前行,进入下一行
}
if (r < n)//出现了0 = ?的情况,没有唯一解
{
for (int i = r; i < n; i++)
if (fabs(a[i][n]) > EPS) return 2;//0 = 非零,无解
return 1;//否则后面的方程全为0 = 0,无穷多解
}
//如果有唯一解,那么从后往前更新每个b
for (int i = n - 1; i >= 0; i--)
for (int j = i + 1; j < n; j++)
a[i][n] -= a[i][j] * a[j][n];
return 0;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j <= n; j++)
cin >> a[i][j];
int t = gauss();
if (t == 0) for (int i = 0; i < n; i++) printf("%.2lf\n", a[i][n]);
else if (t == 1) puts("Infinite group solutions");
else puts("No solution");
return 0;
}
二、AcWing 884. 高斯消元解异或线性方程组
【题目描述】
输入一个包含
n
n
n个方程
n
n
n个未知数的异或线性方程组。
方程组中的系数和常数为
0
0
0或
1
1
1,每个未知数的取值也为
0
0
0或
1
1
1。
求解这个方程组。
异或线性方程组示例如下:
{ a 11 x 1 ∧ a 12 x 2 ∧ ⋯ ∧ a 1 n x n = b 1 a 21 x 1 ∧ a 22 x 2 ∧ ⋯ ∧ a 2 n x n = b 2 … a m 1 x 1 ∧ a m 2 x 2 ∧ ⋯ ∧ a m n x n = b m \left\{ \begin{aligned} &a_{11}x_1\wedge a_{12}x_2\wedge \dots \wedge a_{1n}x_n=b_1 \\ &a_{21}x_1\wedge a_{22}x_2\wedge \dots \wedge a_{2n}x_n=b_2 \\ &\dots \\ &a_{m1}x_1\wedge a_{m2}x_2\wedge \dots \wedge a_{mn}x_n=b_m \end{aligned} \right. ⎩ ⎨ ⎧a11x1∧a12x2∧⋯∧a1nxn=b1a21x1∧a22x2∧⋯∧a2nxn=b2…am1x1∧am2x2∧⋯∧amnxn=bm
【输入格式】
第一行包含整数
n
n
n。
接下来
n
n
n行,每行包含
n
+
1
n+1
n+1个整数
0
0
0或
1
1
1,表示一个方程的
n
n
n个系数以及等号右侧的常数。
【输出格式】
如果给定线性方程组存在唯一解,则输出共
n
n
n行,其中第
i
i
i行输出第
i
i
i个未知数的解。
如果给定线性方程组存在多组解,则输出Multiple sets of solutions
。
如果给定线性方程组无解,则输出No solution
。
【数据范围】
1
≤
n
≤
100
1≤n≤100
1≤n≤100
【输入样例】
3
1 1 0 1
0 1 1 0
1 0 0 1
【输出样例】
1
0
0
【分析】
高斯消元求解异或线性方程组的基本思想和求解普通线性方程组基本一样,甚至还更简单:
- 枚举每一列 c c c
- 找到当前列为 1 1 1的任意一行
- 把这一行换到最上面(未确定阶梯型的行,并不是第一行)
- 将下面所有行的当且列的值变成 0 0 0,即和这一行异或即可(其余所有的数字依次跟着变化)
【代码】
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int a[N][N];
int n;
int gauss()//返回0表示有唯一解,1表示有无穷多组解,2表示无解
{
int r, c;//分别表示行、列
for (r = 0, c = 0; c < n; c++)
{
//第一步:找出第c列中为1的任意一行
int t = r;
for (int i = r; i < n; i++)
if (a[i][c]) { t = i; break; }
if (!a[t][c]) continue;//如果所有方程的这一列都为0那么不进行操作
//第二步:将这一行换至第一行(未确定方程的第一行),即第r行
for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]);
//第三步:将这一行之后的每一行的第c列的数变成0
for (int i = r + 1; i < n; i++)
if (a[i][c])
for (int j = c; j <= n; j++)
a[i][j] ^= a[r][j];
r++;//处理完当前行,进入下一行
}
if (r < n)
{
for (int i = r; i < n; i++)
if (a[i][n]) return 2;
return 1;
}
for (int i = n - 1; i >= 0; i--)
for (int j = i + 1; j < n; j++)
a[i][n] ^= a[i][j] & a[j][n];
return 0;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j <= n; j++)
cin >> a[i][j];
int t = gauss();
if (t == 0) for (int i = 0; i < n; i++) cout << a[i][n] << endl;
else if (t == 1) puts("Multiple sets of solutions");
else puts("No solution");
return 0;
}