高斯消元 & 線性基
本來說不寫了,但還是寫點吧
[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 V\quad a+b \in V\)
\(\forall a\in V,\ k \in F\quad ka\in 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]