'commit'
This commit is contained in:
82
TangDou/AcWing/Gauss/208.cpp
Normal file
82
TangDou/AcWing/Gauss/208.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int n;
|
||||
int a[N][N];
|
||||
|
||||
int gauss() {
|
||||
int r = 1;
|
||||
for (int c = 1; c <= n; c++) {
|
||||
int t = r;
|
||||
for (int i = r + 1; i <= n; i++)
|
||||
if (a[i][c]) t = i;
|
||||
if (!a[t][c]) continue;
|
||||
swap(a[t], a[r]);
|
||||
for (int i = r + 1; i <= n; i++)
|
||||
for (int j = n + 1; j >= c; j--)
|
||||
if (a[i][c]) a[i][j] ^= a[r][j];
|
||||
r++;
|
||||
}
|
||||
|
||||
int res = 1;
|
||||
// 此时已经到了全零行
|
||||
if (r < n + 1) {
|
||||
for (int i = r; i <= n; i++) {
|
||||
// 全零行的右边出现非零 无解
|
||||
if (a[i][n + 1]) return -1; // 出现了 0 == !0,无解
|
||||
// 如果出现了0=0这样的情况,可能是 0x1+0x2+0x3 这样的情况,
|
||||
// 此时,不管x1,x3取什么值(0,1),都与结果无关,所以自由元数量的2次方就是答案
|
||||
// 比如x1=0,x1=1-->2个答案
|
||||
// 比如x2=0,x2=1-->2个答案
|
||||
// 比如x3=0,x3=1....
|
||||
// 同时这些 x1,x2,x3的取值是可以随意取的,每个有2种取法,是一个典型的乘法原理,即2*2*2*...,数量就是自由元的数量
|
||||
// 现在就是循环中,所以,可以利用循环,每次乘2就完成了2次方的计算
|
||||
res <<= 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int T;
|
||||
cin >> T; // T组测试数据
|
||||
while (T--) {
|
||||
memset(a, 0, sizeof a); // 多组测试数据,不清空OI一场空
|
||||
cin >> n; // 开关个数
|
||||
for (int i = 1; i <= n; i++) cin >> a[i][n + 1]; // 初始状态
|
||||
for (int i = 1; i <= n; i++) { // 终止状态
|
||||
int t; // 第i个开关的终止状态
|
||||
cin >> t;
|
||||
// s1: 1号开关的初始状态 t1:1号开关的结束状态
|
||||
// x1 x2 x3 ... xn 1~n个开关是否按下,0:不按下,1:按下
|
||||
// a13:3号开关影响1号开关状态, a1n:n号开关影响1号开关状态.
|
||||
// 推导的方程
|
||||
// 含义:从初始状态 s1开始出发,最终到达t1这个状态。
|
||||
// 有些开关是可以影响1号开关的最终状态,有些变化了也不影响。我们把开关之间的关联关系设为a_ij,描述j开关变化,可以影响到i开关
|
||||
// 如果 a_ij=0,表示j开关不会影响i开关,不管x_j=1,还是x_j=0都无法影响i开关的状态。
|
||||
|
||||
// s1^ a11*x1 ^ a12*x2 ^ a13*x3 ^ ... ^a1n*xn=t1
|
||||
// <=>
|
||||
// s1^ s1 ^ a11*x1 ^ a12*x2 ^ a13*x3 ^ ... ^a1n*xn= t1 ^ s1
|
||||
// <=>
|
||||
// a11*x1 ^ a12*x2 ^ a13*x3 ^ ... ^a1n*xn= t1 ^ s1
|
||||
|
||||
// 这里初始化时 a[1][n+1]就是s1,下面这行的意思就是 t1 ^ s1
|
||||
a[i][n + 1] ^= t; // 在维护增广矩阵的最后一列数值
|
||||
a[i][i] = 1; // 第i个开关一定会改变第i个灯,形成一个三角?
|
||||
}
|
||||
|
||||
int x, y;
|
||||
while (cin >> x >> y, x && y) a[y][x] = 1; // 操作开关x,x影响y。生成左侧方程系数。给定的是1,未说明的是0
|
||||
// 这个矩阵系数,第一维的是行,第二维的是列
|
||||
// 上面的输入,其实是反的,比如它说,3影响1,其实真正的含义是a_13=1
|
||||
|
||||
// 系数矩阵准备完毕,可以用高斯消元求解方程了
|
||||
int t = gauss();
|
||||
if (t == -1)
|
||||
puts("Oh,it's impossible~!!");
|
||||
else
|
||||
printf("%d\n", t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
248
TangDou/AcWing/Gauss/208.md
Normal file
248
TangDou/AcWing/Gauss/208.md
Normal file
@@ -0,0 +1,248 @@
|
||||
##[$AcWing$ $208$. 开关问题(高斯消元)](https://www.acwing.com/problem/content/description/210/)
|
||||
|
||||
[视频讲解](https://www.bilibili.com/video/av679114156)
|
||||
|
||||
### 一、题目描述
|
||||
有 $N$ 个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开。
|
||||
|
||||
你的目标是经过若干次开关操作后使得最后 $N$ 个开关达到一个特定的状态。
|
||||
|
||||
对于任意一个开关,**最多只能进行一次开关操作**。
|
||||
|
||||
你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)
|
||||
|
||||
**输入格式**
|
||||
输入第一行有一个数 $K$,表示以下有 $K$ 组测试数据。
|
||||
|
||||
每组测试数据的格式如下:
|
||||
|
||||
第一行:一个数 $N$。
|
||||
|
||||
第二行:$N$ 个 $0$ 或者 $1$ 的数,表示开始时 $N$ 个开关状态。
|
||||
|
||||
第三行:$N$ 个 $0$ 或者 $1$ 的数,表示操作结束后 $N$ 个开关的状态。
|
||||
|
||||
接下来每行两个数 $I,J$,表示如果操作第 $I$ 个开关,第 $J$ 个开关的状态也会变化。
|
||||
|
||||
每组数据以 $0$ $0$ 结束。
|
||||
|
||||
**输出格式**
|
||||
每组数据输出占一行。
|
||||
|
||||
如果有可行方法,输出总数,否则输出 `Oh,it's impossible~!!` 。
|
||||
|
||||
**数据范围**
|
||||
$1≤K≤10,0<N<29$
|
||||
|
||||
**输入样例:**
|
||||
```cpp {.line-numbers}
|
||||
2
|
||||
3
|
||||
0 0 0
|
||||
1 1 1
|
||||
1 2
|
||||
1 3
|
||||
2 1
|
||||
2 3
|
||||
3 1
|
||||
3 2
|
||||
0 0
|
||||
3
|
||||
0 0 0
|
||||
1 0 1
|
||||
1 2
|
||||
2 1
|
||||
0 0
|
||||
```
|
||||
|
||||
**输出样例:**
|
||||
```cpp {.line-numbers}
|
||||
4
|
||||
Oh,it's impossible~!!
|
||||
```
|
||||
|
||||
### 二、异或知识
|
||||
|
||||
**灯**,有一个明显的特征,原来是亮的,按一下就灭了;原来是灭的,按一下就亮了。
|
||||
这明显就是一个 **异或** 操作,比如 `0 ^ 1 = 1`,`1 ^ 1 = 0`,也就是,不管原来是啥样,直接异或一个$1$就可以达到 **目标状态**。
|
||||
|
||||
|
||||
* 一个值与自身的异或 总是为 $0$
|
||||
```c++
|
||||
x ^ x = 0
|
||||
```
|
||||
* 一个值与 $0$ 异或 等于本身
|
||||
```c++
|
||||
x ^ 0 = x
|
||||
```
|
||||
* 可交换性
|
||||
```c++
|
||||
a ^ b = b ^ a
|
||||
```
|
||||
|
||||
根据以上的四个特点
|
||||
我们可以推导:
|
||||
```c++
|
||||
a ^ b = c
|
||||
等式两边都增加对b的异或, 等价于
|
||||
a ^ b ^ b = c ^ b
|
||||
等式左边的 b^b=0, a^0=a, 所以有
|
||||
a = c ^ b
|
||||
最终相当于把 b 从等号左边转到等号右边来了.
|
||||
```
|
||||
上面的推论,一会在下面的解题中将会用到。
|
||||
|
||||
### 三、题目解析
|
||||
|
||||
**[前导知识:高斯消元求解异或方程组](https://www.cnblogs.com/littlehb/p/15386303.html)**
|
||||
|
||||
|
||||
对于任意一个开关,至多可以进行一次操作!**这个非常重要,否则不断的关停,方案数就没头了**~
|
||||
|
||||
我们使用 $x_1、x_2、x_3$ 分别表示 **是不是操作这个开关**,没操作是$0$,操作了就是$1$。
|
||||
|
||||
以题目给出的例子来理解一下:
|
||||
|
||||
初始状态$0,0,0$,目标状态$1,1,1$。
|
||||
|
||||
* 打开一个开关
|
||||
* 假设我们操作第一个开关,题目中说了,操作$1$的话,开关$1,2,3$都会变化。初始状态都是$0$,开一下开关$1$,则$1,2,3$全亮了,达到目标状态,看来只开开关$1$就是一种方案。
|
||||
|
||||
* 假设我们操作第二个开关,题目中说了,操作$2$的话,开关$1,2,3$都会变化。初始状态都是$0$,开一下开关$2$,则$1,2,3$全亮了,达到目标状态,看来只开开关$2$就是一种方案。
|
||||
|
||||
* 假设我们操作第三个开关,题目中说了,操作$3$的话,开关$1,2,3$都会变化。初始状态都是$0$,开一下开关$3$,则$1,2,3$全亮了,达到目标状态,看来只开开关$2$就是一种方案。
|
||||
|
||||
* 打开两个开关
|
||||
* 如果我们选择两个打开,比如$1,2$,那么$1$打开所有的灯,结果被$2$全关了,不是目标状态。我们选择其它任意两个,都是一样的效果,结论就是选择两个打开是不行的。
|
||||
|
||||
* 打开三个开关
|
||||
$1$打开,全亮;$2$打开,全灭;$3$打开,全亮。$OK$!
|
||||
|
||||
所以,结论就是有四种情况,分别是:只开$1$,只开$2$,只开$3$,$3$个全开。
|
||||
至此,示例数据理解了。按数学的形式写一下就是
|
||||
$x_1=1,x_2=0,x_3=0$
|
||||
$x_1=0,x_2=1,x_3=0$
|
||||
$x_1=0,x_2=0,x_3=1$
|
||||
$x_1=1,x_2=1,x_3=1$
|
||||
|
||||
按上面这样按下开关,就可以从状态$(0,0,0)$到达目标状态$(1,1,1)$。
|
||||
|
||||
我们只用数学符号$x_i$来描述某个开关是否进行了操作,还不足以描述整个事情,为什么呢?因为在数学公式中,**没有体现出谁影响谁这个关键问题**!需要进一步的进行抽象整理:
|
||||
|
||||
> 设$a_{ij}$表示当$j$开关按下时,相应的第$i$个开关也要发生变化。
|
||||
|
||||
**举个栗子**
|
||||
对于开关$1$,假设初始状态为$s$,目标状态是$t$,那么它会如何变化到$t$的呢?
|
||||
肯定是操作了自己,或者是,操作了那些能影响它的开关~!
|
||||
|
||||
#### ① $a_{i,j}$: $j$按下,会影响开关$i$
|
||||
它自己如何表示呢? 就是$a_{11}$嘛,而且$a_{11}$肯定是$1$,因为根据异或的性质,只有是$1$才能保证按下后出现相反状态!
|
||||
影响的开关怎么表示呢?就是$a_{1j}=1$啊!这样才会表示$j$按下,影响开关$1$。而$a_{1j}=0$就是表示$j$不会影响$1$。
|
||||
|
||||
$$
|
||||
\large \left\{\begin{matrix}
|
||||
a_{11}=1 \\
|
||||
s \bigoplus a_{11} \cdot x_1 \bigoplus a_{12} \cdot x_2...\bigoplus a_{1n}\cdot x_n =t
|
||||
\end{matrix}\right.
|
||||
$$
|
||||
|
||||
#### ② $x_i$: $i$号开关是否按下
|
||||
这个玩意怎么理解呢?
|
||||
* 比如$x_1=1$,因为$a_{11}=1$,所以当$1$号开关按下时,它的状态将会变化,可能是由$0$到$1$,也可能是由$1$到$0$。
|
||||
* 比如$x_n=1$,表示第$n$个开关操作了,但是,由于$a_{1n}=0$,也就是根据题意知道,操作$n$号开关,$1$号开关不会变化,那么$n$的变化,不会影响$1$后开关最后的状态。
|
||||
|
||||
进一下抽象,把这样$n$个开关的状态变化关系列出来,就是一个异或方程组,剩下的就是高斯消元求解异或方程组了。
|
||||
|
||||
|
||||
这里面还需要一个小的变化:
|
||||
```cpp {.line-numbers}
|
||||
x^y^z=t => y^z=t^x
|
||||
```
|
||||
这样就可以把$x$移动到右侧去。
|
||||
|
||||
|
||||
#### $Code$
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int n;
|
||||
int a[N][N];
|
||||
|
||||
int gauss() {
|
||||
int r = 1;
|
||||
for (int c = 1; c <= n; c++) {
|
||||
int t = r;
|
||||
for (int i = r + 1; i <= n; i++)
|
||||
if (a[i][c]) t = i;
|
||||
if (!a[t][c]) continue;
|
||||
swap(a[t], a[r]);
|
||||
for (int i = r + 1; i <= n; i++)
|
||||
for (int j = n + 1; j >= c; j--)
|
||||
if (a[i][c]) a[i][j] ^= a[r][j];
|
||||
r++;
|
||||
}
|
||||
|
||||
int res = 1;
|
||||
// 此时已经到了全零行
|
||||
if (r < n + 1) {
|
||||
for (int i = r; i <= n; i++) {
|
||||
// 全零行的右边出现非零 无解
|
||||
if (a[i][n + 1]) return -1; // 出现了 0 == !0,无解
|
||||
// 如果出现了0=0这样的情况,可能是 0x1+0x2+0x3 这样的情况,
|
||||
// 此时,不管x1,x3取什么值(0,1),都与结果无关,所以自由元数量的2次方就是答案
|
||||
// 比如x1=0,x1=1-->2个答案
|
||||
// 比如x2=0,x2=1-->2个答案
|
||||
// 比如x3=0,x3=1....
|
||||
// 同时这些 x1,x2,x3的取值是可以随意取的,每个有2种取法,是一个典型的乘法原理,即2*2*2*...,数量就是自由元的数量
|
||||
// 现在就是循环中,所以,可以利用循环,每次乘2就完成了2次方的计算
|
||||
res <<= 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int T;
|
||||
cin >> T; // T组测试数据
|
||||
while (T--) {
|
||||
memset(a, 0, sizeof a); // 多组测试数据,不清空OI一场空
|
||||
cin >> n; // 开关个数
|
||||
for (int i = 1; i <= n; i++) cin >> a[i][n + 1]; // 初始状态
|
||||
for (int i = 1; i <= n; i++) { // 终止状态
|
||||
int t; // 第i个开关的终止状态
|
||||
cin >> t;
|
||||
// s1: 1号开关的初始状态 t1:1号开关的结束状态
|
||||
// x1 x2 x3 ... xn 1~n个开关是否按下,0:不按下,1:按下
|
||||
// a13:3号开关影响1号开关状态, a1n:n号开关影响1号开关状态.
|
||||
// 推导的方程
|
||||
// 含义:从初始状态 s1开始出发,最终到达t1这个状态。
|
||||
// 有些开关是可以影响1号开关的最终状态,有些变化了也不影响。我们把开关之间的关联关系设为a_ij,描述j开关变化,可以影响到i开关
|
||||
// 如果 a_ij=0,表示j开关不会影响i开关,不管x_j=1,还是x_j=0都无法影响i开关的状态。
|
||||
|
||||
// s1^ a11*x1 ^ a12*x2 ^ a13*x3 ^ ... ^a1n*xn=t1
|
||||
// <=>
|
||||
// s1^ s1 ^ a11*x1 ^ a12*x2 ^ a13*x3 ^ ... ^a1n*xn= t1 ^ s1
|
||||
// <=>
|
||||
// a11*x1 ^ a12*x2 ^ a13*x3 ^ ... ^a1n*xn= t1 ^ s1
|
||||
|
||||
// 这里初始化时 a[1][n+1]就是s1,下面这行的意思就是 t1 ^ s1
|
||||
a[i][n + 1] ^= t; // 在维护增广矩阵的最后一列数值
|
||||
a[i][i] = 1; // 第i个开关一定会改变第i个灯,形成一个三角?
|
||||
}
|
||||
|
||||
int x, y;
|
||||
while (cin >> x >> y, x && y) a[y][x] = 1; // 操作开关x,x影响y。生成左侧方程系数。给定的是1,未说明的是0
|
||||
// 这个矩阵系数,第一维的是行,第二维的是列
|
||||
// 上面的输入,其实是反的,比如它说,3影响1,其实真正的含义是a_13=1
|
||||
|
||||
// 系数矩阵准备完毕,可以用高斯消元求解方程了
|
||||
int t = gauss();
|
||||
if (t == -1)
|
||||
puts("Oh,it's impossible~!!");
|
||||
else
|
||||
printf("%d\n", t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
276
TangDou/AcWing/Gauss/883.md
Normal file
276
TangDou/AcWing/Gauss/883.md
Normal file
@@ -0,0 +1,276 @@
|
||||
##[$AcWing$ $883$. 高斯消元解线性方程组](https://www.acwing.com/problem/content/885/)
|
||||
|
||||
### 一、题目描述
|
||||
|
||||
输入一个包含 $n$ 个方程 $n$ 个未知数的线性方程组。
|
||||
|
||||
方程组中的系数为实数。
|
||||
|
||||
求解这个方程组。
|
||||
|
||||
下图为一个包含 $m$ 个方程 $n$ 个未知数的线性方程组示例:
|
||||
|
||||
<center><img src='https://cdn.acwing.com/media/article/image/2019/06/27/19_b30080c698-9a504fc2d5628535be9dcb5f90ef76c6a7ef634a.gif'></center>
|
||||
|
||||
**输入格式**
|
||||
第一行包含整数 $n$。
|
||||
|
||||
接下来 $n$ 行,每行包含 $n+1$ 个实数,表示一个方程的 $n$ 个系数以及等号右侧的常数。
|
||||
|
||||
**输出格式**
|
||||
如果给定线性方程组存在唯一解,则输出共 $n$ 行,其中第 $i$ 行输出第 $i$ 个未知数的解,结果保留两位小数。
|
||||
|
||||
如果给定线性方程组存在无数解,则输出 `Infinite group solutions`。
|
||||
|
||||
如果给定线性方程组无解,则输出 `No solution`。
|
||||
|
||||
**数据范围**
|
||||
$1≤n≤100$,所有输入系数以及常数均保留两位小数,绝对值均不超过 $100$。
|
||||
|
||||
**输入样例:**
|
||||
```cpp {.line-numbers}
|
||||
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
|
||||
```
|
||||
|
||||
**输出样例:**
|
||||
```cpp {.line-numbers}
|
||||
1.00
|
||||
-2.00
|
||||
3.00
|
||||
```
|
||||
|
||||
|
||||
### 二、线性方程组知识
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101213140828.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzOTU3NjAz,size_16,color_FFFFFF,t_70'></center>
|
||||
|
||||
|
||||
### 三、高斯消元法
|
||||
求解线性方程组的办法,是高斯消元法:
|
||||
|
||||
- 通过一系列的加减消元,得到类似 $kx=b$ 的式子,**求得最后一个未知量的结果**
|
||||
|
||||
- 然后逐一回代求解 **整个** $x$ 向量
|
||||
|
||||
以下列方程为例:
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101222034134.png'></center>
|
||||
|
||||
第一次加减消元,用第$1$式子消去后面所有的$x$得到:
|
||||
|
||||
<font color='red' size=3><b>方法:第$①$式左右两边除以$2$,然后左右两边乘以$②,③$式中$x$的系数,再分别加(减)到$②,③$式中,我称之为系数清零消元法</b></font>
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101222126837.png'></center>
|
||||
|
||||
第二次加减消元,用第$2$个式子消去后面所有的$y$得到:
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101222155464.png'></center>
|
||||
|
||||
这样就完成了高斯消元的步骤$1$,形成了一个倒三角形的形状,接下来逐一回代即可。
|
||||
|
||||
#### 用矩阵表示高斯消元
|
||||
(1)消元过程:
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101222532792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzOTU3NjAz,size_16,color_FFFFFF,t_70'></center>
|
||||
|
||||
(2)**无解**: 当消元完毕后,发现有一行系数都为 $0$,但是常数项不为 $0$,此时无解
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101222621561.png'></center>
|
||||
|
||||
(3)**多解**: 当消元完毕后,发现有多行系数、常数项均为 $0$,此时多解,有几行为全为 $0$,就有几个自由元,即变量的值可以任取,有无数种情况可以满足给出的方程组
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210101222735259.png'></center>
|
||||
|
||||
此时自由元为$2$个
|
||||
|
||||
|
||||
#### 常见问题
|
||||
**问题一: 为什么化简为 $1$的操作,和清零的操作都要倒着推?**
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210529170727201.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JldHRsZV9raW5n,size_16,color_FFFFFF,t_70'></center>
|
||||
|
||||
当然也可以正着推,**不过要用一个变量来记录一下开头的元素的值**。化简都除以这个值就行了,不过有点麻烦,倒着推时要以省一个变量~
|
||||
|
||||
**问题二:`if (abs(a[t][c]) < eps) continue;`如何理解?**
|
||||
> 假设 $c$表示列,$r$表示行,此时我们进行到了 $c=2$ $r=2$
|
||||
```cpp {.line-numbers}
|
||||
1 0 2 3
|
||||
0 0 3 2
|
||||
0 0 2 3
|
||||
```
|
||||
|
||||
你会发现此时$r$行之下 的$c$列的绝对值最大值就是$0$.
|
||||
说明此时的第$c$列已经化简好了,那么不需要再进行后面的化简操作,
|
||||
但是此时第$r$行不用变,此时$c$加$1$ 就接着从 **第二行** **第三列** 开始找绝对值最大的数。
|
||||
如果我们的$r$向后移动了,那么此时我们的第$2$行是没有化简的,这显然是不对的。
|
||||
以此为例,$r$如果向后移动了,此时$r=3,c=3$但是此时我们的 **第二行** **第三列** 是 $3$ 并不是$1$这种最简的形态。
|
||||
|
||||
**问题三:倒着推解是如何来的**
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210529171526712.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JldHRsZV9raW5n,size_16,color_FFFFFF,t_70'></center>
|
||||
|
||||
当有唯一解的时候,我们最后的化简一定是这种。
|
||||
解的最终形式,如下所示:
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/20210529171810354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JldHRsZV9raW5n,size_16,color_FFFFFF,t_70'></center>
|
||||
|
||||
我们倒着将每一行都简成每一行只有一个$1$ 的形式。 这里模拟一下,代码就懂了。
|
||||
|
||||
### 5、手绘流程
|
||||
<center><img src='https://cdn.acwing.com/media/article/image/2020/10/20/42785_6e4a478212-2AC12ACC19AF7566CEB46399BCF82BFC.jpg'></center>
|
||||
|
||||
#### $Code$ 下标从$1$开始 【推荐】
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 110;
|
||||
const double eps = 1e-8; // 小数的精度值
|
||||
int n; // n个方程
|
||||
double a[N][N]; // 系数+结果矩阵
|
||||
|
||||
int gauss() { // 高斯消元,答案存于a[i][n]中,0 <= i < n
|
||||
int r = 1; // 先按行后按列进行计算,当前行是第1行
|
||||
for (int c = 1; c <= n; c++) { // 枚举每一列
|
||||
int t = r; // 防破坏r,复制出t
|
||||
for (int i = r; i <= n; i++) // 当前行需要找它的后续行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i; // t的任务是找出c列中系数最大值是哪一行
|
||||
if (abs(a[t][c]) < eps) continue; // 如果c列绝对值最大的系数是0, 那么处理下一列
|
||||
for (int i = c; i <= n + 1; i++) swap(a[t][i], a[r][i]); // 将绝对值最大的行与当前行交换
|
||||
for (int i = n + 1; i >= c; i--) a[r][i] /= a[r][c]; // a[r][c]:行首系数,将当前行的行首通过除法变为1,倒序
|
||||
for (int i = r + 1; i <= n; i++) // 用当前行r的c列,通过减法将后续行c列消成0
|
||||
for (int j = n + 1; j >= c; j--) // 倒序,需要保留行首,逻辑和上面是一样的,行首值是变更系数,如果正序就把系数变成1了,后面就不对了
|
||||
a[i][j] -= a[r][j] * a[i][c]; // a[i][c]:需要变化的乘法系数,减法:对位相消
|
||||
r++; // 下一行
|
||||
}
|
||||
if (r <= n) { // 如果没有成功执行完所有行,意味着中间存在continue,也就是某一列的系数都是0
|
||||
for (int i = r; i <= n; i++)
|
||||
if (abs(a[i][n + 1]) > eps) return 0; // 系数是0,但结果不是0,无解
|
||||
return 2; // 系数是0,结果也是0,x取啥都对,有无穷多组解
|
||||
}
|
||||
// 代回求每个变量值
|
||||
for (int i = n - 1; i; i--) // 行,倒序
|
||||
for (int j = i + 1; j <= n; j++) // 列,倒三角,右上角应该都是0,对角线全是1
|
||||
a[i][n + 1] -= a[i][j] * a[j][n + 1]; // 系数消为0
|
||||
return 1; // 有唯一解
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 1; i <= n; i++) // n个方程
|
||||
for (int j = 1; j <= n + 1; j++) // 每行n+1个数据,因为最后一列是等号右侧值
|
||||
cin >> a[i][j];
|
||||
|
||||
int t = gauss();
|
||||
if (t == 0)
|
||||
puts("No solution");
|
||||
else if (t == 2)
|
||||
puts("Infinite group solutions");
|
||||
else
|
||||
for (int i = 1; i <= n; i++) printf("%.2lf\n", a[i][n + 1]); // 保留两位小数
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### $Code$ 下标从$0$开始【不推荐】
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 110;
|
||||
const double eps = 1e-8; // 小数的精度值
|
||||
int n; // n个方程
|
||||
double a[N][N]; // 系数+结果矩阵
|
||||
|
||||
int gauss() { // 高斯消元,答案存于a[i][n]中,0 <= i < n
|
||||
int r = 0; // 先按行后按列进行计算,当前行是第1行
|
||||
for (int c = 0; c < n; c++) { // 枚举每一列
|
||||
int t = r; // 防破坏r,复制出t
|
||||
for (int i = r; i < n; i++) // 当前行需要找它的后续行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i; // t的任务是找出c列中系数最大值是哪一行
|
||||
if (abs(a[t][c]) < eps) continue; // 如果c列绝对值最大的系数是0, 那么处理下一列
|
||||
for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]); // 将绝对值最大的行与当前行交换
|
||||
for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; // a[r][c]:行首系数,将当前行的行首通过除法变为1,倒序
|
||||
for (int i = r + 1; i < n; i++) // 用当前行r的c列,通过减法将后续行c列消成0
|
||||
for (int j = n; j >= c; j--) // 倒序,需要保留行首,逻辑和上面是一样的,行首值是变更系数,如果正序就把系数变成1了,后面就不对了
|
||||
a[i][j] -= a[r][j] * a[i][c]; // a[i][c]:需要变化的乘法系数,减法:对位相消
|
||||
r++; // 下一行
|
||||
}
|
||||
if (r < n) { // 如果没有成功执行完所有行,意味着中间存在continue,也就是某一列的系数都是0
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][n]) > eps) return 0; // 系数是0,但结果不是0,无解
|
||||
return 2; // 系数是0,结果也是0,x取啥都对,有无穷多组解
|
||||
}
|
||||
// 代回求每个变量值
|
||||
for (int i = n - 2; i >= 0; i--) // 行,倒序
|
||||
for (int j = i + 1; j < n; j++) // 列,倒三角,右上角应该都是0,对角线全是1
|
||||
a[i][n] -= a[i][j] * a[j][n]; // 系数消为0
|
||||
return 1; // 有唯一解
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 0; i < n; i++) // n个方程
|
||||
for (int j = 0; j <= n; j++) // 每行n+1个数据,因为最后一列是等号右侧值
|
||||
cin >> a[i][j];
|
||||
|
||||
int t = gauss();
|
||||
if (t == 0)
|
||||
puts("No solution");
|
||||
else if (t == 2)
|
||||
puts("Infinite group solutions");
|
||||
else
|
||||
for (int i = 0; i < n; i++) printf("%.2lf\n", a[i][n]); // 保留两位小数
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 五. 经验教训
|
||||
练习题:**[$P3389$ 【模板】高斯消元法](https://www.luogu.com.cn/problem/P3389)**
|
||||
1、一定要复制题目中输出的字符串,我就是因为`No Solution` -> `No solution`挂了第一个点
|
||||
2、$Luogu$的模板题中,没有强制区分无解和无穷多组解。
|
||||
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 110;
|
||||
const double eps = 1e-8; // 小数的精度值
|
||||
int n; // n个方程
|
||||
double a[N][N]; // 系数+结果矩阵
|
||||
|
||||
int gauss() { // 高斯消元,答案存于a[i][n]中,0 <= i < n
|
||||
int r = 1; // 先按行后按列进行计算,当前行是第1行
|
||||
for (int c = 1; c <= n; c++) { // 枚举每一列
|
||||
int t = r; // 防破坏r,复制出t
|
||||
for (int i = r; i <= n; i++) // 当前行需要找它的后续行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i; // t的任务是找出c列中系数最大值是哪一行
|
||||
if (abs(a[t][c]) < eps) continue; // 如果c列绝对值最大的系数是0, 那么处理下一列
|
||||
for (int i = c; i <= n + 1; i++) swap(a[t][i], a[r][i]); // 将绝对值最大的行与当前行交换
|
||||
for (int i = n + 1; i >= c; i--) a[r][i] /= a[r][c]; // a[r][c]:行首系数,将当前行的行首通过除法变为1,倒序
|
||||
for (int i = r + 1; i <= n; i++) // 用当前行r的c列,通过减法将后续行c列消成0
|
||||
for (int j = n + 1; j >= c; j--) // 倒序,需要保留行首,逻辑和上面是一样的,行首值是变更系数,如果正序就把系数变成1了,后面就不对了
|
||||
a[i][j] -= a[r][j] * a[i][c]; // a[i][c]:需要变化的乘法系数,减法:对位相消
|
||||
r++; // 下一行
|
||||
}
|
||||
if (r <= n) { // 如果没有成功执行完所有行,意味着中间存在continue,也就是某一列的系数都是0
|
||||
for (int i = r; i <= n; i++)
|
||||
if (abs(a[i][n + 1]) > eps) return 0; // 系数是0,但结果不是0,无解
|
||||
return 2; // 系数是0,结果也是0,x取啥都对,有无穷多组解
|
||||
}
|
||||
// 代回求每个变量值
|
||||
for (int i = n - 1; i; i--) // 行,倒序
|
||||
for (int j = i + 1; j <= n; j++) // 列,倒三角,右上角应该都是0,对角线全是1
|
||||
a[i][n + 1] -= a[i][j] * a[j][n + 1]; // 系数消为0
|
||||
return 1; // 有唯一解
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 1; i <= n; i++) // n个方程
|
||||
for (int j = 1; j <= n + 1; j++) // 每行n+1个数据,因为最后一列是等号右侧值
|
||||
cin >> a[i][j];
|
||||
|
||||
int t = gauss();
|
||||
if (t == 0 || t==2)
|
||||
puts("No Solution");
|
||||
else
|
||||
for (int i = 1; i <= n; i++) printf("%.2lf\n", a[i][n + 1]); // 保留两位小数
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
49
TangDou/AcWing/Gauss/883_0.cpp
Normal file
49
TangDou/AcWing/Gauss/883_0.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 110;
|
||||
const double eps = 1e-8; // 小数的精度值
|
||||
int n; // n个方程
|
||||
double a[N][N]; // 系数+结果矩阵
|
||||
|
||||
int gauss() { // 高斯消元,答案存于a[i][n]中,0 <= i < n
|
||||
int r = 0; // 先按行后按列进行计算,当前行是第1行
|
||||
for (int c = 0; c < n; c++) { // 枚举每一列
|
||||
int t = r; // 防破坏r,复制出t
|
||||
for (int i = r; i < n; i++) // 当前行需要找它的后续行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i; // t的任务是找出c列中系数最大值是哪一行
|
||||
if (abs(a[t][c]) < eps) continue; // 如果c列绝对值最大的系数是0, 那么处理下一列
|
||||
for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]); // 将绝对值最大的行与当前行交换
|
||||
for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; // a[r][c]:行首系数,将当前行的行首通过除法变为1,倒序
|
||||
for (int i = r + 1; i < n; i++) // 用当前行r的c列,通过减法将后续行c列消成0
|
||||
for (int j = n; j >= c; j--) // 倒序,需要保留行首,逻辑和上面是一样的,行首值是变更系数,如果正序就把系数变成1了,后面就不对了
|
||||
a[i][j] -= a[r][j] * a[i][c]; // a[i][c]:需要变化的乘法系数,减法:对位相消
|
||||
r++; // 下一行
|
||||
}
|
||||
if (r < n) { // 如果没有成功执行完所有行,意味着中间存在continue,也就是某一列的系数都是0
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][n]) > eps) return 0; // 系数是0,但结果不是0,无解
|
||||
return 2; // 系数是0,结果也是0,x取啥都对,有无穷多组解
|
||||
}
|
||||
// 代回求每个变量值
|
||||
for (int i = n - 2; i >= 0; i--) // 行,倒序
|
||||
for (int j = i + 1; j < n; j++) // 列,倒三角,右上角应该都是0,对角线全是1
|
||||
a[i][n] -= a[i][j] * a[j][n]; // 系数消为0
|
||||
return 1; // 有唯一解
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 0; i < n; i++) // n个方程
|
||||
for (int j = 0; j <= n; j++) // 每行n+1个数据,因为最后一列是等号右侧值
|
||||
cin >> a[i][j];
|
||||
|
||||
int t = gauss();
|
||||
if (t == 0)
|
||||
puts("No solution");
|
||||
else if (t == 2)
|
||||
puts("Infinite group solutions");
|
||||
else
|
||||
for (int i = 0; i < n; i++) printf("%.2lf\n", a[i][n]); // 保留两位小数
|
||||
return 0;
|
||||
}
|
||||
49
TangDou/AcWing/Gauss/883_1.cpp
Normal file
49
TangDou/AcWing/Gauss/883_1.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 110;
|
||||
const double eps = 1e-8; // 小数的精度值
|
||||
int n; // n个方程
|
||||
double a[N][N]; // 系数+结果矩阵
|
||||
|
||||
int gauss() { // 高斯消元,答案存于a[i][n]中,0 <= i < n
|
||||
int r = 1; // 先按行后按列进行计算,当前行是第1行
|
||||
for (int c = 1; c <= n; c++) { // 枚举每一列
|
||||
int t = r; // 防破坏r,复制出t
|
||||
for (int i = r; i <= n; i++) // 当前行需要找它的后续行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i; // t的任务是找出c列中系数最大值是哪一行
|
||||
if (abs(a[t][c]) < eps) continue; // 如果c列绝对值最大的系数是0, 那么处理下一列
|
||||
for (int i = c; i <= n + 1; i++) swap(a[t][i], a[r][i]); // 将绝对值最大的行与当前行交换
|
||||
for (int i = n + 1; i >= c; i--) a[r][i] /= a[r][c]; // a[r][c]:行首系数,将当前行的行首通过除法变为1,倒序
|
||||
for (int i = r + 1; i <= n; i++) // 用当前行r的c列,通过减法将后续行c列消成0
|
||||
for (int j = n + 1; j >= c; j--) // 倒序,需要保留行首,逻辑和上面是一样的,行首值是变更系数,如果正序就把系数变成1了,后面就不对了
|
||||
a[i][j] -= a[r][j] * a[i][c]; // a[i][c]:需要变化的乘法系数,减法:对位相消
|
||||
r++; // 下一行
|
||||
}
|
||||
if (r <= n) { // 如果没有成功执行完所有行,意味着中间存在continue,也就是某一列的系数都是0
|
||||
for (int i = r; i <= n; i++)
|
||||
if (abs(a[i][n + 1]) > eps) return 0; // 系数是0,但结果不是0,无解
|
||||
return 2; // 系数是0,结果也是0,x取啥都对,有无穷多组解
|
||||
}
|
||||
// 代回求每个变量值
|
||||
for (int i = n - 1; i; i--) // 行,倒序
|
||||
for (int j = i + 1; j <= n; j++) // 列,倒三角,右上角应该都是0,对角线全是1
|
||||
a[i][n + 1] -= a[i][j] * a[j][n + 1]; // 系数消为0
|
||||
return 1; // 有唯一解
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 1; i <= n; i++) // n个方程
|
||||
for (int j = 1; j <= n + 1; j++) // 每行n+1个数据,因为最后一列是等号右侧值
|
||||
cin >> a[i][j];
|
||||
|
||||
int t = gauss();
|
||||
if (t == 0)
|
||||
puts("No solution");
|
||||
else if (t == 2)
|
||||
puts("Infinite group solutions");
|
||||
else
|
||||
for (int i = 1; i <= n; i++) printf("%.2lf\n", a[i][n + 1]); // 保留两位小数
|
||||
return 0;
|
||||
}
|
||||
57
TangDou/AcWing/Gauss/884.cpp
Normal file
57
TangDou/AcWing/Gauss/884.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// 异或:不进位的加法
|
||||
const int N = 110;
|
||||
int n;
|
||||
int a[N][N];
|
||||
|
||||
void gauss() {
|
||||
int r = 1;
|
||||
for (int c = 1; c <= n; c++) {
|
||||
int t = r;
|
||||
// 找到第一个是1的行,不用找最大值,因为只有0、1,1就是最大值
|
||||
for (int i = r + 1; i <= n; i++)
|
||||
if (a[i][c]) t = i;
|
||||
// 没有找到的话,下一列
|
||||
if (!a[t][c]) continue;
|
||||
// 交换
|
||||
swap(a[r], a[t]);
|
||||
|
||||
for (int i = r + 1; i <= n; i++) // 后面的行
|
||||
if (a[i][c]) // 如果与c同一列有数字1
|
||||
for (int j = n + 1; j >= c; j--) // 从右侧结果开始,到c列为止,利用当前的r行c列值1,通过异或运算,将后续行此列消为0
|
||||
a[i][j] ^= a[r][j];
|
||||
// 下一行
|
||||
r++;
|
||||
}
|
||||
// 判断无解和无穷多解
|
||||
if (r <= n) {
|
||||
for (int i = r; i <= n; i++)
|
||||
if (a[i][n + 1]) {
|
||||
// 无解
|
||||
puts("No solution");
|
||||
exit(0);
|
||||
}
|
||||
// 无穷多组解
|
||||
puts("Multiple sets of solutions");
|
||||
exit(0);
|
||||
}
|
||||
// 唯一解,还原成各个方程的解
|
||||
for (int i = n; i >= 1; i--)
|
||||
for (int j = i + 1; j <= n; j++)
|
||||
a[i][n + 1] ^= a[i][j] * a[j][n + 1];
|
||||
// 这个乘法用的好!因为a[i][j]只有01两种形式,0乘以什么都是0,异或后还是本身。1的话就相当于异或一下等式左右两端
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 1; i <= n; i++)
|
||||
for (int j = 1; j <= n + 1; j++)
|
||||
cin >> a[i][j];
|
||||
gauss();
|
||||
// 唯一解
|
||||
for (int i = 1; i <= n; i++) cout << a[i][n + 1] << endl;
|
||||
return 0;
|
||||
}
|
||||
135
TangDou/AcWing/Gauss/884.md
Normal file
135
TangDou/AcWing/Gauss/884.md
Normal file
@@ -0,0 +1,135 @@
|
||||
##[$AcWing$ $884$. 高斯消元解异或线性方程组](https://www.acwing.com/problem/content/description/886/)
|
||||
|
||||
### 一、题目描述
|
||||
输入一个包含 $n$ 个方程 $n$ 个未知数的异或线性方程组。
|
||||
|
||||
方程组中的系数和常数为 $0$ 或 $1$,每个未知数的取值也为 $0$ 或 $1$。
|
||||
|
||||
求解这个方程组。
|
||||
|
||||
异或线性方程组示例如下:
|
||||
|
||||
```cpp {.line-numbers}
|
||||
M[1][1]x[1] ^ M[1][2]x[2] ^ … ^ M[1][n]x[n] = B[1]
|
||||
M[2][1]x[1] ^ M[2][2]x[2] ^ … ^ M[2][n]x[n] = B[2]
|
||||
…
|
||||
M[n][1]x[1] ^ M[n][2]x[2] ^ … ^ M[n][n]x[n] = B[n]
|
||||
```
|
||||
其中 `^` 表示异或$(XOR)$,$M[i][j]$ 表示第 $i$
|
||||
个式子中 $x[j]$ 的系数,$B[i]$ 是第 $i$ 个方程右端的常数,取值均为 $0$ 或 $1$。
|
||||
|
||||
**输入格式**
|
||||
第一行包含整数 $n$。
|
||||
|
||||
接下来 $n$ 行,每行包含 $n+1$ 个整数 $0$ 或 $1$
|
||||
,表示一个方程的 $n$ 个系数以及等号右侧的常数。
|
||||
|
||||
**输出格式**
|
||||
如果给定线性方程组存在唯一解,则输出共 $n$ 行,其中第 $i$ 行输出第 $i$ 个未知数的解。
|
||||
|
||||
如果给定线性方程组存在多组解,则输出 `Multiple sets of solutions`。
|
||||
|
||||
如果给定线性方程组无解,则输出 `No solution`。
|
||||
|
||||
**数据范围**
|
||||
$1≤n≤100$
|
||||
|
||||
**输入样例**:
|
||||
```cpp {.line-numbers}
|
||||
3
|
||||
1 1 0 1
|
||||
0 1 1 0
|
||||
1 0 0 1
|
||||
```
|
||||
|
||||
**输出样例:**
|
||||
```cpp {.line-numbers}
|
||||
1
|
||||
0
|
||||
0
|
||||
```
|
||||
|
||||
### 二、解题思路
|
||||
**核心思想**: 异或-不进位的加法
|
||||
那么等式与等式间的异或要一起进行才能保证等式左右两边依然是相等关系!
|
||||
`a^b^c = x`
|
||||
`d^f = y`
|
||||
则
|
||||
`a^b^d^c^f = x^y`
|
||||
|
||||
#### 1 左下角消$0$
|
||||
① 枚举列
|
||||
② 找第一个非零行
|
||||
③ 交换
|
||||
④ 把同列下面行消零(异或)
|
||||
|
||||
#### 2 判断$3$种情况
|
||||
① 唯一解
|
||||
② 秩<$n$
|
||||
- 有矛盾 无解
|
||||
- 无矛盾 无穷多解
|
||||
|
||||
#### 3 手绘流程
|
||||

|
||||
|
||||
|
||||
#### $Code$
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// 异或:不进位的加法
|
||||
const int N = 110;
|
||||
int n;
|
||||
int a[N][N];
|
||||
|
||||
void gauss() {
|
||||
int r = 1;
|
||||
for (int c = 1; c <= n; c++) {
|
||||
int t = r;
|
||||
// 找到第一个是1的行,不用找最大值,因为只有0、1,1就是最大值
|
||||
for (int i = r + 1; i <= n; i++)
|
||||
if (a[i][c]) t = i;
|
||||
// 没有找到的话,下一列
|
||||
if (!a[t][c]) continue;
|
||||
// 交换
|
||||
swap(a[r], a[t]);
|
||||
|
||||
for (int i = r + 1; i <= n; i++) // 后面的行
|
||||
if (a[i][c]) // 如果与c同一列有数字1
|
||||
for (int j = n + 1; j >= c; j--) // 从右侧结果开始,到c列为止,利用当前的r行c列值1,通过异或运算,将后续行此列消为0
|
||||
a[i][j] ^= a[r][j];
|
||||
// 下一行
|
||||
r++;
|
||||
}
|
||||
// 判断无解和无穷多解
|
||||
if (r <= n) {
|
||||
for (int i = r; i <= n; i++)
|
||||
if (a[i][n + 1]) {
|
||||
// 无解
|
||||
puts("No solution");
|
||||
exit(0);
|
||||
}
|
||||
// 无穷多组解
|
||||
puts("Multiple sets of solutions");
|
||||
exit(0);
|
||||
}
|
||||
// 唯一解,还原成各个方程的解
|
||||
for (int i = n; i >= 1; i--)
|
||||
for (int j = i + 1; j <= n; j++)
|
||||
a[i][n + 1] ^= a[i][j] * a[j][n + 1];
|
||||
// 这个乘法用的好!因为a[i][j]只有01两种形式,0乘以什么都是0,异或后还是本身。1的话就相当于异或一下等式左右两端
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n;
|
||||
for (int i = 1; i <= n; i++)
|
||||
for (int j = 1; j <= n + 1; j++)
|
||||
cin >> a[i][j];
|
||||
gauss();
|
||||
// 唯一解
|
||||
for (int i = 1; i <= n; i++) cout << a[i][n + 1] << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
BIN
TangDou/AcWing/Gauss/884.xlsx
Normal file
BIN
TangDou/AcWing/Gauss/884.xlsx
Normal file
Binary file not shown.
64
TangDou/AcWing/Gauss/P3389.cpp
Normal file
64
TangDou/AcWing/Gauss/P3389.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
|
||||
const double eps = 1e-8;
|
||||
double a[N][N];
|
||||
int n;
|
||||
int gauss() {
|
||||
int c, r;
|
||||
for (c = 0, r = 0; c < n; c++) {
|
||||
int t = r;
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i;
|
||||
|
||||
if (abs(a[t][c]) < eps) continue;
|
||||
|
||||
if (r != t) swap(a[t], a[r]);
|
||||
|
||||
for (int i = n; i >= c; i--) a[r][i] /= a[r][c];
|
||||
|
||||
for (int i = r + 1; i < n; i++)
|
||||
for (int j = n; j >= c; j--)
|
||||
a[i][j] -= a[r][j] * a[i][c];
|
||||
r++;
|
||||
}
|
||||
|
||||
if (r < n) {
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][n]) > eps)
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = n - 2; 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();
|
||||
//经验教训:
|
||||
// 1、一定要复制题目中输出的字符串,我就是因为"No Solution" -> "No solution"挂了第一个点
|
||||
// 2、Luogu的模板题中,没有强制区分无解和无穷多组解。
|
||||
if (t == 2 || t == 1)
|
||||
puts("No Solution");
|
||||
// else if (t == 1)
|
||||
// puts("Infinite group solutions");
|
||||
else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (abs(a[i][n]) < eps) a[i][n] = 0;
|
||||
printf("%.2lf\n", a[i][n]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
93
TangDou/AcWing/Gauss/POJ1830.cpp
Normal file
93
TangDou/AcWing/Gauss/POJ1830.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <cstdio>
|
||||
using namespace std;
|
||||
|
||||
const int N = 30;
|
||||
const double eps = 10e-8;
|
||||
int n;
|
||||
|
||||
int start[N]; //开始状态
|
||||
int stop[N]; //结束状态
|
||||
|
||||
//高斯消元模板
|
||||
double a[N][N]; //增广矩阵
|
||||
int gauss() {
|
||||
int c, r; //当前列,行
|
||||
// 1、枚举系数每一列
|
||||
for (c = 0, r = 0; c < n; c++) {
|
||||
// 2、找出系数最大的行
|
||||
int t = r;
|
||||
for (int i = r; i < n; i++) //行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i;
|
||||
// 3、最大系数为0,直接下一列
|
||||
if (abs(a[t][c]) < eps) continue;
|
||||
// 4、交换
|
||||
if (r != t) // POJ中,如果二维数组,直接swap(a[t],a[r])会报编译错误,没办法,只好用了循环
|
||||
for (int i = 0; i < N; i++) swap(a[t][i], a[r][i]);
|
||||
// 5、倒序, 每项除a[r][c],化系数为1,处理的是方程左右两端,需要带着a[r][n]
|
||||
for (int i = n; i >= c; i--) a[r][i] /= a[r][c];
|
||||
// 6、用当前行将下面所有的列消成0
|
||||
for (int i = r + 1; i < n; i++)
|
||||
for (int j = n; j >= c; j--)
|
||||
a[i][j] -= a[r][j] * a[i][c];
|
||||
// 7、下一行
|
||||
r++;
|
||||
}
|
||||
|
||||
if (r < n) {
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][n]) > eps)
|
||||
return -1; //无解
|
||||
return n - r; //自由元个数
|
||||
}
|
||||
|
||||
//倒三角,将已知解代入
|
||||
for (int i = n - 2; i >= 0; i--)
|
||||
for (int j = i + 1; j < n; j++)
|
||||
a[i][n] -= a[i][j] * a[j][n];
|
||||
|
||||
//唯一解:0
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int T;
|
||||
cin >> T;
|
||||
while (T--) {
|
||||
cin >> n;
|
||||
memset(a, 0, sizeof(a));
|
||||
memset(start, 0, sizeof(start)); //初始化开始状态
|
||||
memset(stop, 0, sizeof(stop)); //初始化终止状态
|
||||
//输入起始状态(下标从0开始)
|
||||
for (int i = 0; i < n; i++) cin >> start[i];
|
||||
|
||||
//输入终止状态(下标从0开始)
|
||||
for (int i = 0; i < n; i++) cin >> stop[i];
|
||||
|
||||
//输入增广矩阵
|
||||
int x, y;
|
||||
while (cin >> x >> y && x != 0 && y != 0)
|
||||
a[y - 1][x - 1] = 1; //反着存入y-1受x-1影响
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i][n] = start[i] ^ stop[i]; //状态变化 start^stop
|
||||
a[i][i] = 1; //自己影响自己
|
||||
}
|
||||
|
||||
//高斯消元模板
|
||||
int t = gauss();
|
||||
|
||||
if (t == -1)
|
||||
cout << "Oh,it's impossible~!!" << endl;
|
||||
else
|
||||
cout << (1 << t) << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
174
TangDou/AcWing/Gauss/POJ1830.md
Normal file
174
TangDou/AcWing/Gauss/POJ1830.md
Normal file
@@ -0,0 +1,174 @@
|
||||
poj 1830 开关问题
|
||||
[题目传送门](http://poj.org/problem?id=1830)
|
||||
|
||||
### 1、描述
|
||||
|
||||
有一些开始状态的开关,题目让我们操控开关,使得开关从开始状态变成指定状态。
|
||||
|
||||
注意,当你操作一个开关,其关联的开关也会被操控。例如输入样例一,开始状态为$000$的三个开关,你要操作使其变成$111$。那么有以下四种方法:
|
||||
* 只打开开关$1$,$2$ $and$ $3$和$1$关联,所以$2$ $and$ $3$也变成$1$
|
||||
* 只打开开关$2$,$3$
|
||||
* 只打开开关$3$,$4$
|
||||
* 打开开关$1,2,3$
|
||||
|
||||
> 初始状态 $0$ $0$ $0$ $0$
|
||||
终止状态 $1$ $1$ $1$ $1$
|
||||
按下$i$号开关,会影响$j$号开关:
|
||||
$1$ $3$
|
||||
$3$ $1$
|
||||
$3$ $4$
|
||||
$4$ $1$
|
||||
$4$ $3$
|
||||
我能影响谁$a_i$
|
||||
$a_1$->(1,0,1,0)->{$1$,$3$}
|
||||
$a_2$->(0,1,0,0)->{$2$}
|
||||
$a_3$->{$1,3,4$}
|
||||
$a_4$->{$1,3,4$}
|
||||
谁能影响我$k_i$
|
||||
|
||||
|
||||
操作每个开关后,影响的情况
|
||||
|
||||
### 2、问题解决
|
||||
我们用**线性方程组**来求解。
|
||||
|
||||
$$\large [a_1,a_2,a_3] * \begin{bmatrix}
|
||||
x_1 \\
|
||||
x_2 \\
|
||||
x_3
|
||||
\end{bmatrix} =b,即A_x=b
|
||||
$$
|
||||
|
||||
由上图表达式,我们可以思考,倘若**乘号左边是开关的关联关系**,**乘号右边是开关操作**,那么两个矩阵相乘,**结果就是开关变化**。由此,我们可以让$b$为开关变化。
|
||||
意思为当开关从$0$变为$1$,那么**开关有变化**,$b_i$为$1$;**当开关无变化**,则$b_i$为$0$。
|
||||
|
||||
<center><img src='https://img-blog.csdn.net/20150803170327397'></center>
|
||||
|
||||
例如上面例子,$a_1=(1,0,1,0)$表示,操作开关$1$,开关$3$也会改变。即$a[i][j]$表示操作开关$j$,开关$i$也会变化。
|
||||
|
||||
所以,我们可以列出矩阵乘法表达式,然后进行高斯消元求解。
|
||||
|
||||
<font color='red'><b>$Q$:为什么这里有反着记录的呢?</b></font>
|
||||
答:我们希望以方程组的形式对原问题进行求解。那么,由于有$n $盏灯,每个方程都要描述一盏灯的变化情况,这样的话,就共需要$n$个方程。
|
||||
我们以第$1$盏灯为例,我们知道它的初始状态和终止状态,我们就可以研究它是在初始状态下,通过什么样的操作变化到终止状态的。因为灯开关的特殊性,关联一次就变化一次,所以,我们还可以取一巧:看看开始状态和终止状态是一样的呢,还是有了变化。
|
||||
|
||||
* 一样的
|
||||
中间的变化是偶数次。用异或运算来描述就是异或和等于$0$。
|
||||
|
||||
* 不一样
|
||||
中间的变化是奇数次。用异或运算来描述就是异或和等$1$。
|
||||
|
||||
**总结:我们关心的是状态的变化情况**
|
||||
|
||||
$1$号灯的变化,受和它相关联灯的制约,以上面的图为例说明:
|
||||
第$1$盏灯的变化,受$1,3,4$三个灯的影响,我们可以把$1$盏灯的变化情况看作
|
||||
$$\large 1*x_1+0*x_2+1*x_3+1*x_4$$
|
||||
描述的说是:
|
||||
* $1$号灯操作,影响$1$号灯的状态
|
||||
* $2$号灯操作,不影响$1$号灯的状态
|
||||
* $3$号灯操作,影响$1$号灯的状态
|
||||
* $4$号灯操作,影响$1$号灯的状态
|
||||
|
||||
当然,只是说影响,但每个灯还有是权力决定自己是不是要操作的。
|
||||
这就引出了一个系数的问题:
|
||||
“**我是一号灯,谁能影响我的状态?**”
|
||||
|
||||
这玩意和题目中给的“**我是$x$号灯,我能影响$y$号灯**”是反着的,输入的时候要 <font color='red'><b>小心识别</b></font> 。
|
||||
|
||||
|
||||
|
||||
```c++
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <cstdio>
|
||||
using namespace std;
|
||||
|
||||
const int N = 30;
|
||||
const double eps = 10e-8;
|
||||
int n;
|
||||
|
||||
int start[N]; //开始状态
|
||||
int stop[N]; //结束状态
|
||||
|
||||
//高斯消元模板
|
||||
double a[N][N]; //增广矩阵
|
||||
int gauss() {
|
||||
int c, r; //当前列,行
|
||||
// 1、枚举系数每一列
|
||||
for (c = 0, r = 0; c < n; c++) {
|
||||
// 2、找出系数最大的行
|
||||
int t = r;
|
||||
for (int i = r; i < n; i++) //行
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i;
|
||||
// 3、最大系数为0,直接下一列
|
||||
if (abs(a[t][c]) < eps) continue;
|
||||
// 4、交换
|
||||
if (r != t) // POJ中,如果二维数组,直接swap(a[t],a[r])会报编译错误,没办法,只好用了循环
|
||||
for (int i = 0; i < N; i++) swap(a[t][i], a[r][i]);
|
||||
// 5、倒序, 每项除a[r][c],化系数为1,处理的是方程左右两端,需要带着a[r][n]
|
||||
for (int i = n; i >= c; i--) a[r][i] /= a[r][c];
|
||||
// 6、用当前行将下面所有的列消成0
|
||||
for (int i = r + 1; i < n; i++)
|
||||
for (int j = n; j >= c; j--)
|
||||
a[i][j] -= a[r][j] * a[i][c];
|
||||
// 7、下一行
|
||||
r++;
|
||||
}
|
||||
|
||||
if (r < n) {
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][n]) > eps)
|
||||
return -1; //无解
|
||||
return n - r; //自由元个数
|
||||
}
|
||||
|
||||
//倒三角,将已知解代入
|
||||
for (int i = n - 2; i >= 0; i--)
|
||||
for (int j = i + 1; j < n; j++)
|
||||
a[i][n] -= a[i][j] * a[j][n];
|
||||
|
||||
//唯一解:0
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int T;
|
||||
cin >> T;
|
||||
while (T--) {
|
||||
cin >> n;
|
||||
memset(a, 0, sizeof(a));
|
||||
memset(start, 0, sizeof(start)); //初始化开始状态
|
||||
memset(stop, 0, sizeof(stop)); //初始化终止状态
|
||||
//输入起始状态(下标从0开始)
|
||||
for (int i = 0; i < n; i++) cin >> start[i];
|
||||
|
||||
//输入终止状态(下标从0开始)
|
||||
for (int i = 0; i < n; i++) cin >> stop[i];
|
||||
|
||||
//输入增广矩阵
|
||||
int x, y;
|
||||
while (cin >> x >> y && x != 0 && y != 0)
|
||||
a[y - 1][x - 1] = 1; //反着存入y-1受x-1影响
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i][n] = start[i] ^ stop[i]; //状态变化 start^stop
|
||||
a[i][i] = 1; //自己影响自己
|
||||
}
|
||||
|
||||
//高斯消元模板
|
||||
int t = gauss();
|
||||
|
||||
if (t == -1)
|
||||
cout << "Oh,it's impossible~!!" << endl;
|
||||
else
|
||||
cout << (1 << t) << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
65
TangDou/AcWing/Gauss/Test.cpp
Normal file
65
TangDou/AcWing/Gauss/Test.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 110;
|
||||
const double eps = 1e-8;
|
||||
|
||||
int n;
|
||||
double a[N][N];
|
||||
|
||||
/*
|
||||
高斯消元,答案存于a[i][n]中,0 <= i < n
|
||||
0:唯一解 1:无穷多组解 2:无解
|
||||
*/
|
||||
int gauss() {
|
||||
int c, r;
|
||||
for (c = 0, r = 0; c < n; c++) { //枚举的是列
|
||||
int t = r;
|
||||
for (int i = r; i < n; i++) // 找绝对值最大的行,可以解决精度问题
|
||||
if (abs(a[i][c]) > abs(a[t][c])) t = i;
|
||||
|
||||
if (abs(a[t][c]) < eps) continue; //是0不处理
|
||||
|
||||
swap(a[t], a[r]); // 将绝对值最大的行换到最顶端
|
||||
for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; // 将当前行的首位变成1,倒着来
|
||||
for (int i = r + 1; i < n; i++) // 用当前行将下面所有的列消成0
|
||||
for (int j = n; j >= c; j--)
|
||||
a[i][j] -= a[r][j] * a[i][c];
|
||||
|
||||
r++;
|
||||
}
|
||||
|
||||
if (r < n) {
|
||||
for (int i = r; i < n; i++)
|
||||
if (abs(a[i][n]) > eps)
|
||||
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 + 1; j++) //带等式右侧的结果值
|
||||
cin >> a[i][j];
|
||||
|
||||
int t = gauss();
|
||||
if (t == 2) //无解
|
||||
puts("No solution");
|
||||
else if (t == 1) //无穷多组
|
||||
puts("Infinite group solutions");
|
||||
else {
|
||||
//唯一解
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (abs(a[i][n]) < eps) a[i][n] = 0; // 去掉输出 -0.00 的情况
|
||||
printf("%.2lf\n", a[i][n]);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user