线性代数 —— 线性基与前缀线性基
【概述】
線性基,是線性代數中的概念,在信息學競賽中,前綴線性基是線性基的擴展,他們主要用于處理有關異或和的極值問題。
一組線性無關的向量即可作為一組基底,張起一個線性的向量空間,這個基底即稱為線性基,利用線性基的基底進行線性運算,可表示向量空間內的所有向量,換句話說,所有向量都可以拆成基底的線性組合。
根據異或的原理,將一個數字拆成他的二進制形式,將二進制形式用向量來表示,由于一組線性無關的向量可以張起一個向量空間,因此可以考慮構造這樣一組數字的二進制形式組成的線性基,在這個線性基中,通過基底的線性組合、異或運算,從而可以表達所有的異或結果。
簡單來說,若一個數集 T 的值域范圍為?,那么 T 的線性基是 T 的一個子集 A={a1,a2,a3,...,an},A 中元素相互異或而成的集合,等價于原數集 T 的元素相互異或形成異或集合
【線性基】
1.構造
線性基具有以下幾個性質:
- 原數列中的任意一個數,均可由線性基中的一些數異或得到
- 線性基中的數的個數唯一,且在保證唯一性的前提下,數的個數是最少的
- 線性基的異或集合中,每個元素的異或方案唯一?,且異或集合中不存在 0
- 線性基中元素互相異或,異或集合不變
- 線性基二進制最高位互不相同
1)插入
由于 xor 偶數具有抵消性,因此可利用這個性質對加入的向量進行修改。
對于每一個數來說,需要判斷其是否能加入到線性基中,將新加入的數字 x 拆為二進制形式,從最高位向最低位掃描其為 1 的二進制位,掃描到第 i 位時,如果線性基向量組 d[i] 中不存在,即 d[i]=0 時,就令 d[i]=x,然后 break;反之,若線性基向量組 d[i] 中存在,即 d[i]=1 時,就令 x=x xor d[i]
這樣在掃描完成后,新加入的數字 x,要么被添加進線性基,要么經過一系列操作后變成了 0,如果變成了 0,說明這個數字 x 對應的二進制向量不屬于極大線性無關組。
LL d[60+5];//線性基 void add(LL x) {for(int i=60; i>=0; i--) {if(x&(1LL<<i)) {if(d[i])//插入失敗,異或x^=d[i];else {//插入成功,保存后退出d[i]=x;break;}}} }2)合并
當兩個線性基需要合并時,直接將一個線性基暴力的插入另一個線性基即可。
LL d1[60+5],d2[60+5];//合并前的兩個線性基 LL d[60+5];//合并后的線性基 void Union(){memcpy(d,d1,sizeof(d1));for(int i=0;i<=60;i++){if(d2[i])add(d2[i]);} }2.查詢
在線性基構造完畢后,即可對線性基進行查詢。
1)最大值
當要查詢最大值時,就從高位向低位掃描線性基,如果異或后使得結果變大,就異或進答案中。
LL d[60+5];//線性基 LL queryMax() {LL res=0;for(int i=60; i>=0; i--)if((res^d[i])>res)res^=d[i];return res; }2)最小值
當要查詢最小值時,最低位上的線性基即為答案。
LL d[60+5];//線性基 LL queryMin(){for(int i=0; i<=60; i++)if(d[i])return d[i];return 0; }3)第 k 小值
由于線性基二進制最高位互不相同,因此要將線性基改造為每一位相互獨立。
從最高位向最低位枚舉,如果 i<j,那么 d[j] 的第 i 位是 1,就將 a[j] 異或上 a[i],經過一系列操作后,對于二進制的某一位 i,只有 a[i] 這一位是 1,其余的都是 0
這樣在查詢的時候,將 k 進行二進制拆分,對于為 1 的位,就異或上對應的線性基,最終得到的就是第 k 小的值
注:如果要求第 k 大的值,也就是求第 n-k+1 小的值,將 queryKth() 的參數 k 換為 n-k+1 即可
LL d[60+5];//線性基 LL p[60+5];//改造后的線性基 int cnt; void rebuild() {//改造線性基for(int i=60; i>=0; i--)for(int j=i-1; j>=0; j--)if(d[i]&(1LL<<j))d[i]^=d[j];for(int i=0; i<=60; i++)if(d[i])p[cnt++]=d[i]; } LL queryKth(LL k){//查詢第k小的值,若要查k大,將k換為n-k+1即可int res=0;if(k>=(1LL<<cnt))return -1;for(int i=60; i>=0; i--)if(k&(1LL<<i))res^=p[i];return res; }【前綴線性基】
線性基用于處理整個區間異或的極值問題,前綴線性基用來維護整個區間中的一段區間異或的極值問題。
1.構造
前綴線性基建立在線性基的基礎上,其是對于 i 都建立一個 1~i? 數組成的線性基。
假設有 4 個數 1、2、3、4,那么就建立 4 個基底,分別為:
- 第一基底:1
- 第二基底:1、2
- 第三基底:1、2、3
- 第四基底:1、2、3、4
在構造 i 個基底時,先將前 i-1 個 基復制過來,然后再加上第 i 個數,來構造一個新的基底。
如果對于基底的某個位置上的數有能更新的數,就進行替換,以保證對于某個右端點 R 能盡可能的用靠近 R 的數作為基,也就保證了所有 [L,R] 中的數都盡可能的參與構造這個數。
例如:對于第五個基的第 3 個位置,如果第 4 個數和第 2 個數都能放在這個位置,那么就放第 4 個數,因為查詢區間 [3,5] 時能用 3、4、5 構造這個數,包含了 4;查詢區間 [2,5] 時,能用 2、3、4、5 構造這個數
也就是說對于 [2,5] 來說,第 2 個數還是第 4 個數都可以使用,但是 [3,5] 來說,只能使用第 4 個數,因此當第 4 個數和第 2 個數都能放在第 3 個位置時,優先用第 4 個數
void add(int x) { //向線性基中添加xnum++;for(int i=0; i<32; i++) { //復制前num-1個線性基d[num][i]=d[num-1][i];pos[num][i]=pos[num-1][i];}int P=num;for(int i=31; i>=0; i--) {if((x>>i)&1) {if(d[num][i]) { //插入失敗if(pos[num][i]<P) { //交換位置swap(pos[num][i], P);swap(d[num][i],x);}x^=d[num][i];//異或} else { //插入成功d[num][i]=x;pos[num][i]=P;break;}}} }2.查詢
在查詢 [L,R] 區間時,就找到第 R 個基,然后用?pos[i] 來記錄第 i 個位置是哪個數,如果 pos<l,那么就說明不是區間 [L,R] 的基
1)最大值
int queryMax(int l,int r) { //查詢[l,r]中的最大值int res=0;for (int i=31; i>=0; i--) {if(pos[r][i]<l)continue;if ((res^d[r][i])>res)res^=d[r][i];}return res; }2)最小值
int queryMin(int l,int r) {//查詢[l,r]中的最小值for(int i=0; i<=60; i++) {if(pos[r][i]<l)continue;if(d[r][i])return d[r][i];}return 0; }【模版】
1.線性基
struct LinearBasis {int num;//線性基中元素個數LL d[60+5];//線性基LL p[60+5];//查詢第k小時的改造的線性基int cnt;//改造線性基用LinearBasis() {memset(d,0,sizeof(d));memset(p,0,sizeof(p));num=0;cnt=0;}bool add(LL x){for(int i=60; i>=0; i--) {if(x&(1LL<<i)) {if(d[i])//插入失敗,異或x^=d[i];else {//插入成功,保存后退出num++;//統計線性基中元素個數d[i]=x;break;}}}return x>0;//x>0插入成功}LL queryMax() {//查詢最大值LL res=0;for(int i=60; i>=0; i--)if((res^d[i])>res)res^=d[i];return res;}LL queryMin() {//查詢最小值for(int i=0; i<=60; i++)if(d[i])return d[i];return 0;}void rebuild() {//改造線性基for(int i=60; i>=0; i--)for(int j=i-1; j>=0; j--)if(d[i]&(1LL<<j))d[i]^=d[j];for(int i=0; i<=60; i++)if(d[i])p[cnt++]=d[i];}LL queryKth(LL k) {//查詢第k小的值,若要查k大,將k換為n-k+1即可int res=0;if (k>=(1LL<<cnt))return -1;for (int i=60; i>=0; i--)if (k&(1LL<<i))res^=p[i];return res;} }; LinearBasis Union(const LinearBasis &rhs1,const LinearBasis &rhs2) {LinearBasis res=rhs1;for(int i=60; i>=0; i--)if(rhs2.d[i])res.add(rhs1.d[i]);return res; }2.前綴線性基?
struct PrefixLinearBasis{int d[N][32];//前綴線性基int pos[N][32];//最后一個修改i這個位置的數int num;//線性基中元素個數PrefixLinearBasis(){memset(d,0,sizeof(d));memset(pos,0,sizeof(pos));num=0;}void clear(){memset(d,0,sizeof(d));memset(pos,0,sizeof(pos));num=0;}void add(int x){//向線性基中添加xnum++;for(int i=0; i<32; i++){//復制前num-1個線性基d[num][i]=d[num-1][i];pos[num][i]=pos[num-1][i];}int P=num;for(int i=31; i>=0; i--){if((x>>i)&1){if(d[num][i]){//插入失敗if(pos[num][i]<P){//交換位置swap(pos[num][i], P);swap(d[num][i],x);}x^=d[num][i];//異或}else{//插入成功d[num][i]=x;pos[num][i]=P;break;}}}}int queryMax(int l,int r){//查詢[l,r]中的最大值int res=0;for (int i=31; i>=0; i--){if(pos[r][i]<l) continue;if ((res^d[r][i])>res) res^=d[r][i];}return res;}int queryMin(int l,int r) {//查詢[l,r]中的最小值for(int i=0; i<=60; i++){if(pos[r][i]<l)continue;if(d[r][i])return d[r][i];}return 0;} }PLB;【例題】
- 彩燈(洛谷-P3857)(線性基中元素個數):點擊這里
- 元素(HYSBZ-2460)(添加線性基+貪心):點擊這里
同題:元素(洛谷-P4570):點擊這里 - 幸運數字(洛谷-P3292)(最大異或和+LCA):點擊這里
- Operation(HDU-6579)(前綴線性基):點擊這里
- Ivan and Burgers(CF-1100F)(前綴線性基):點擊這里
總結
以上是生活随笔為你收集整理的线性代数 —— 线性基与前缀线性基的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图论 —— 生成树 —— 增量最小生成树
- 下一篇: 钓鱼(信息学奥赛一本通-T1431)