對於一個 \(m\) 維向量組,每一個向量表示為形如 \((x_1,x_2,...,x_m)\)
如果存在一個向量可以用其他向量表示出來,稱為線性相關
否則,稱為線性無關
所有向量組可以形成的向量集合稱為線性空間
求出向量組的一個線性無關的子集,其可以組成的線性空間不變,稱為線性空間的一組基
對於一個向量組,對於其基的求解可以用高斯消元來實現
證明高斯消元的操作對線性空間的大小沒有影響:
- 交換兩行:顯然沒有影響
- 加上另一行的數倍:相當於加上另一個向量,那么這個向量本身可以用新形成的向量表示出來,也沒有影響
於是通過高斯消元求出最大的基底
雖然在 OI 中實數的基底很不常見,但是這是基底的本質
比如這道題可以作為模板:P3265 [JLOI2015]裝備購買
題目中線性無關的限制太明顯了,提示需要構建基底
這道題里由於有了價格的限制,可以先排個序,再把高斯消元的過程動態進行
具體來說是這樣的:從大到小枚舉每一位,如果某一維還沒有基,那么可以直接把這個向量作為那一維的基
否則,將這一維和這一維的基加減抵消成零
代碼實現
#include<bits/stdc++.h>
using namespace std;
const int maxn=505;
int n,m,ans,ans1,b[maxn];
double eps=1e-5;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Node{
double a[maxn];
int val;
}p[maxn];
bool operator < (Node a,Node b){
return a.val<b.val;
}
double ffabs(double x){
return x<0?-x:x;
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>p[i].a[j];
for(int i=1;i<=n;i++)p[i].val=read();
sort(p+1,p+n+1);
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
if(ffabs(p[i].a[j])<eps)continue;
if(!b[j]){
b[j]=i;ans+=p[i].val;ans1++;
break;
}
double chu=p[i].a[j]/p[b[j]].a[j];
for(int k=j;k>=1;k--){
p[i].a[k]-=chu*p[b[j]].a[k];
}
}
}
cout<<ans1<<" "<<ans;
return 0;
}
在 OI 中線性基幾乎特指在異或中的應用
以模板題為例,要求最大異或子集
可以模仿構建基底的過程,從高到低確定每一位,根據二進制的性質,這樣一定是最優的
根據基底的本質來理解,如果構建出線性基,相當於可以異或出原來的數能異或得到的所有數
那么答案直接在線性基上貪心選取每一位即可
- 最后注意一點:大於的優先級是高於異或的哦~
代碼實現
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,x,a[100],ans;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
x=read();
for(int j=50;j>=0;j--){
if((x>>j)&1){
if(a[j])x^=a[j];
else{
a[j]=x;
break;
}
}
}
}
for(int i=50;i>=0;i--)if(!((ans>>i)&1))ans^=a[i];
cout<<ans;
return 0;
}
和上面一樣的套路,先排序,線性基用作判斷能否加入
為了讓剩余火柴的子集不為零,構建線性基,排序后能插入則插入,否則拿走
用到線性基的結論:若基中值有 \(cnt\) 個,那么可以拼湊出的個數為 \(2^{cnt}\)
這道題的不同之處在於不用去重,那么要用到另一個結論,每一個能拼湊的數的拼湊方案數是 \(2^{n-cnt}\),即隨便一個基外的子集都可以添加進來
這就要用到線性基維護圖上問題的新科技了
可以發現由於路徑的可重,那么最終的路徑的一定是一條簡單路徑外加許多環(因為環相當於是一去一回,而重疊部分相互抵消)
於是把所有還放進線性基即可,由於環的個數很多,但是不同環之間可以由異或得出,所以只放返祖邊形成的環即可
另外簡單路徑是可以隨意選的,因為和其他路徑可以通過異或環得出
CF724G Xor-matic Number of the Graph
好的,現在是前面兩天道題的結合版,首先一樣的把所有環放進線性基里。
每一位的貢獻分開考慮
若有環這一位為 \(1\),那么任意兩點異或這個環便可以加上這位貢獻的 \(1\)
方案數為 \(2^{|S|-1}\binom{n}{2}\)
若沒有環這一位為 \(1\),那么只有路徑這一位為 \(1\) 才能產生貢獻
方案數為 \(2^{|S|}cnt(n-cnt)\),其中 \(cnt\) 表示距離這一位為 \(1\) 的點的個數
注意圖可能不聯通
接下來就是線性基的合並了,由於線性基是 \(log\) 位的,那么直接暴力合並 \(log^2\) 即可
操作用線段樹都能維護,直接上即可
發現這次不能維護了,因為修改變成了區間修改
考慮將區間修改變成單點修改,那么差分即可
但是這樣就需要發現差分數組與原數組線性基的關系了
發現原數組展開后差分數組 \(b_{[1,l]}\) 的部分是重疊的,那么可以發現原數組的線性基於 \(a_l\) 加上 \(b_{[l+1,r]}\) 的線性基是等價的
那么用線段樹維護差分數組的線性基,用樹狀數組動態維護原數組的值即可
發現直接合並的復雜度實在太暴躁了,在有些題中不足以通過,那么需要再加入一些小 \(trick\)
考慮離線回答
可以按照右端點排序,只要線性基中的數在左端點右側即可使用
那么每次有沖突時可以貪心地選擇位置靠右的
一樣的套路,維護每個點到根的線性基,深度越深越好,查詢時深度大於 \(lca\) 即可使用