高斯消元 & 线性基
本来说不写了,但还是写点吧
[update 2017-02-18]现在发现真的有好多需要思考的地方,网上很多代码感觉都是错误的,虽然题目通过了
[update 2017-02-19]加入线性基
[update 2017-03-31]完善内容,改用markdown
Gauss Elimination
高斯消元(Gaussian elimination)是求解线性方程组的一种算法,它也可用来求矩阵的秩,以及求可逆方阵的逆矩阵。
它通过逐步消除未知数来将原始线性系统转化为另一个更简单的等价的系统。
它的实质是通过初等行变化(Elementary row operations),将线性方程组的增广矩阵转化为行阶梯矩阵(row echelon form)。
概念
增广矩阵
- 系数矩阵右面加上一列常数
矩阵的初等行变换
- 交换两行
- 给一行乘上一个非零数
- 把一行的倍数加到另一行上
主元
-
从1到n枚举变量作为主元(需要选择这个变量系数绝对值最大的行保证数值稳定性,异或方程、同余方程选非0就可以)
-
和单纯形法类似,主元要写到等式的一边然后用这个方程替换其他方程中的主元,实现上是用矩阵的初等行变换做加减消元
自由元
-
枚举到一个变量,如果剩下的行中这个变量的系数都为0,这个变量就是自由元。
-
自由元存在说明有的方程是线性相关的,加减消元后有的方程就被消掉了。
-
自由元的值一旦确定,其他变元的值就确定了
-
自由元数目是一定的,就是消元结束后全0方程的数目(注意矛盾方程消元后常数项不为0)。
-
自由元集合不一定,但只要枚举一个自由元集合的所有取值就可以得到整个方程组的所有变量取值啦。
关键元
- 每一行的第一个非零元
- 通常来讲这个第i行的关键元应该是第i个。否则说明前面及自己中有自由元。
- 实现上记录pivot[i]为以变元i为关键元的方程在哪一行,也就是变元i用到了哪一行
解线性方程组
过程:
- 消元,不断把系数矩阵变成(REF)(阶梯型矩阵)
- 消元之后回代之前就可以进行无解和多解的判断
- 唯一解形成上三角矩阵,回代即可
实现:
完整的判断多解和无解的版本请见POJ2947
bool Gauss(Matrix a, int n) {
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) return false;
if(r!=i) for(int j=1; j<=n+1; j++) swap(a[r][j], a[i][j]);
for(int j=i+1; j<=n; j++) {
double t = a[j][i]/a[i][i];
for(int k=i; k<=n+1; k++) a[j][k] -= t*a[i][k];
}
}
for(int i=n; i>=1; i--) {
for(int j=n; j>i; j--) a[i][n+1] -= a[i][j]*a[j][n+1];
a[i][n+1] /= a[i][i];
}
return true;
}
解XOR方程组
过程
设N个未知数,M个方程,A为系数矩阵
(now)表示当前到了哪一行
$now = 1 $
(for i = 1 to N)
若存在(j ge now)使得(A_{j , i})为(1)则
交换第(j)行与第(now)行
用第(now)行对之后的行进行消元
(now += 1)
否则第(i)个变量是自由变量,(now)不变
实现
可以使用bitset压位+高斯约当消元
高斯约当消元是选主元之后将主元化为1(xor方程不需要),然后枚举除主元所在行之外所有行消元,可以发现一个过程之后主元所在列就是单位矩阵(对角矩阵)的形式,所以最后不用回代。
void Gauss(bitset<N> a[N], int n) {
now=1;
for(int i=1; i<=n; i++) {
int j=now;
while(j<=n && !a[j][i]) j++;
if(j==n+1) continue;
if(j!=now) swap(a[now], a[j]);
for(int k=1; k<=n; k++)
if(k!=now && a[k][i]) a[k]^=a[now];
now++;
}
now--;
}
解的判断
无解
- 存在矛盾方程,消元后系数全为(0),常数项不为(0)
多解
- 出现了(S)个自由元,这些变量可任意取值从而确定其余变量的值(2^S)组解
- 实现上,now<n说明出现自由元,并且自由元所在方程一定在最后几行。求最优解需要枚举自由元的取值然后回代
唯一解
- 无自由元,即now=n,形成上三角矩阵
对于高斯约当消元
系数矩阵变成了对角矩阵
如果存在自由元,那么大约张这样
O....O
........#...O
...................O
空行
# 位置是一个自由元
这时候也需要枚举自由元的取值然后回代
求矩阵的逆
这里说一种方法,对A进行高斯约当消元,右面的常数列换成单位矩阵。校园后,左面变成了单位矩阵,右面就是(A^{-1})
Matrix inverse(Matrix a) {
Matrix c; c.im();
for(int i=1; i<=n; i++) {
int r;
for(r=i; r<=n; r++) if(a[r][i]) break;
// r != n+1
if(r!=i) for(int j=1; j<=n; j++)
swap(a[i][j], a[r][j]), swap(c[i][j], c[r][j]);
ll inv = Pow(a[i][i], P-2);
for(int j=1; j<=n; j++)
a[i][j] = a[i][j]*inv%P, c[i][j] = c[i][j]*inv%P;
for(int k=1; k<=n; k++) if(k!=i) {
ll t = a[k][i]%P;
for(int j=1; j<=n; j++)
mod(a[k][j] -= a[i][j]*t%P), mod(c[k][j] -= c[i][j]*t%P);
}
}
return c;
}
线性基
概念
线性空间:
设(V)是一个向量集合
(forall a,b in Vquad a+b in V)
(forall ain V, k in Fquad kain V)
线性组合:
线性空间(V)的子集(S={ v_1,v_2,...,v_n }),对于一个(V)中的元素(v),存在一组系数使得
那么(v)是(S)的一个线性组合
线性相关:
线性空间(V)的子集(S={ v_1,v_2,...,v_n }),如果有一组系数使得
(a_1v_1+a_2v_2+...+a_nv_n=0)
则称S是线性相关的
也就是说(S)中的某个向量能被自己所表示出来
线性基:
和小新讲的向量的基底好像啊(就是一个东西吧)
(V)中的任意一个向量都能唯一的表示成(S)中向量的线性组合,那么(S)是(V)的一组基
线性基是满足线性无关的极大子集
线性基中元素的个数(基数)一定,就是线性空间的维度
任一线性无关组的子集线性无关
我们可以用一组向量(S)(不一定线性无关)来构造线性空间,称为(span(S))
矩阵的秩
矩阵可以看成一个向量组,线性基对应着矩阵的秩
矩阵的行秩等于列秩
(行列式不为0 Leftrightarrow 满秩 Leftrightarrow 向量线性无关)
可以使用高斯消元求矩阵(横向量组)的秩(线性基)
拟阵
类似最小生成树
线性无关子集构成一个拟阵,可以用贪心求最大权线性基
异或问题
把每个二进制位看成一个维度,每个整数就是一个向量,向量之间的运算是异或,一个整数的集合就是一个线性空间
插入
用p[i]保存线性基里第i个数位为1的向量(数字)(类似于高斯消元里的每个主元用到哪一行)
待插入x,从高位到低位枚举x为1的二进制位i,如果有p[i]了那么异或p[i]消去x这一位的1,如果没有p[i]那么p[i]=x并推出
如果x最后不是0说明插入成功,否则说明x和已经插入的数字线性相关
注意插入结束后,保证数字p[i]在第i位为1且之后的数字第i位都不是1,不保证在p[i]之前插入的数字第i位不是1,所以在这种线性基上求最大值不能直接异或上要取max
这样可以配合贪心求最大权线性基
合并
将一个全部插入另一个就好了
构造
异或高斯消元后形成对角矩阵,每一位只有一个数字是1
这时候有now个位可以是1,最多有(2^{now})种取值,第i行a[i]保存了第i大的可以是1的二进制位的值
利用构造后的线性基可以求线性空间的最大值,最小值,k小值
- 异或所有a[i]
- a[now],严格次大值就是a[i]^a[now]
- 对k二进制拆分,第i位为1异或上则异或上a[now-i]