线性基学习笔记
线性基学习笔记
——by sunzz3183
引入
学高斯消元后就要学线性基啦!建议先看懂高斯消元!
介绍
给定 \(n\) 个数 \(a_i\),求一个基底。
基底就是一个线性空间,即线性基。
线性基中的 \(t\) 个数 \(p_i\),\(a\) 中的每个数都可以被 \(p\) 的若干数通过异或得出。
求法
高斯消元
直接贴代码:
void add(int num){
for(int i=61;i>=0;i--)
if((num>>i)&1)
if(!p[i]){p[i]=num;return;}
else num^=p[i];
flg=1;//判断有没有0的存在
return;
}
清新易懂,我们从大到小来枚举当前 \(num_{(2)}\) 的第 \(i\) 位,如果发现 \(p_i\) 已经有元素了,就让 \(num=num \oplus p_i\) 否则插入当前的 \(num\)。
问题
线性基通常有如下三个用法:
-
查询一个数是否能被集合中其他数异或表示
-
求一个集合异或最大/最小值
-
求一个集合异或的第k大值
下列给定三种方法的代码
查询
bool ask(){
for(int i=61;i>=0;i--)
if(x>>i&1)x^=p[i];
return !x;
}
最大最小
inline int getmax(){
int ans=0;
for(int i=61;i>=0;i--)
ans=max(ans,ans^p[i]);
return ans;
}
inline int getmin(){
if(flg)return 0;//注意判零的存在
for(int i=0;i<=61;i++)
if(p[i])return p[i];
}
第 k 大
对 \(k\) 进行二进制拆分,然后进行选数。
注意,我们可能会在插入的时候插入了一个类似这样的一个数 \(1111111111_{(2)}\)。那么显然在求第 \(k\) 大的时候,会错序。
所谓我们要对 \(p_i\) 进行重构,让 \(p_i\) 成为 \(10000000_{(2)}\) 这样的数。
void rebuild(){
for(int i=61;i>=0;i--)
for(int j=i-1;j>=0;j--)
if(p[i]>>j&1)p[i]^=p[j];
for(int i=0;i<=61;i++)if(p[i])d[cnt++]=p[i];cnt--;
}
inline int getk(int k){
k-=flg;
if(k>=1ll<<cnt)return -1;
int ans=0,i=0;
while(k)ans^=(k&1)?d[i]:0,k>>=1,i++;
return ans;
}