Linear Basis线性基
话说HAOI 2017八纵八横是个比较裸的线性基,但是那会还没学。。。
目录
基本概念
- 异或和
定义一个无符号整数集合S(注意,我们接下来讨论的集合均指由无符号整数为元素构成的集合),则S的异或和就是S中所有元素互相异或的结果.
通俗的用伪C++
代码表示就是:
unsigned int S[MAXN]={...};
unsigned int xor_sum=0;
for(int i=1;i<MAXN;++i)
xor_sum^=S[i];
张成
定义子集 Ti⊆S ,则所有 Ti 的异或和组成的集合B就是S的张成,记做B=span(S).
通俗的说,就是S的张成在S中任取一些元素求异或和的所有可能结果的异或和;
也可以理解为用S中的元素通过互相异或可以表示的数的集合.线性相关
如果存在一个元素x∈S,且从S中去除出x后S的张成span(S’)包含x,则说S是线性相关的;
简单的说,就是x可由S中的其它某些元素互相异或得来;显然,当S去除出x后,S的张成不变.
线性基
首先,线性基是一个集合,并且更严格的说,一个线性基应该是某个集合的线性基;
我们说一个集合B是集合S的线性基,当且仅当B满足以下条件:
- B⊆span(S);
- B是线性无关的(即不满足线性相关的条件).
也就是说,B中所有元素均可由S中的某些元素互相异或得来;
其实,进一步推理可知:S中的元素也都可以由B中某些元素互相异或得来(提示:x^x=0),并且要想满足这个条件,B中元素已经是缺一不可了;
所以,一个集合的线性基是可以表示这个集合的最小集合.
算法步骤
可以说,线性基的构造用到了贪心的思想,具体如下:
- 插入元素x时,从高位到低位扫;
- 如果线性基集合B[i]!=0,则x^=B[i],消去x的第i位;
- 如果B[i]==0,则B[i]=x,但需注意,要再从高到低把B[j]^=B[i](j>i),消去B[j]的第i位;同时B[i]^=B[j](j
代码
inline void ist(long long x){
for(int i=MAXN-1;i>=0;--i){
if(((x>>i)&1)==0) continue;
if(LB[i]) x^=LB[i];
else{
for(int j=0;j<i;++j){
if((x>>j)&1) x^=LB[j];
}
for(int j=i+1;j<MAXN;++j){
if((LB[j]>>i)&1) LB[j]^=x;
}
LB[i]^=x;
return;
}
}
}
应用
- 子集异或和最大
- 显然先构造线性基;
- 然后贪心,从高到低,如果ans的第i位为0,则异或上LB[i];
long long ans=0;
for(int i=MAXN-1;i>=0;--i){
if(((ans>>i)&1)==0)
ans^=LB[i];
}
- 子集异或和第k小
- 还是先构造一个线性基;
- 但,我们要考虑,对于ans,在高位异或出一个1更能使ans变大;而低位上异或出一个1必然会使ans变大;
- 所以,我们考虑将线性基集合的非0元素从小到大加入一个vector中,然后由于每个线性基元素对ans大小的影响与k中每位相同,我们只需判断k的第i位为1否,然后异或vector[i]就好;
- 注意,线性基子集的非0异或和情况只有 2vector.size() 个,所以我们还要判断是否能异或出第k小.
inline void prepare(){
for(int i=0;i<MAXN;++i){
if(LB[i]) V.push_back(LB[i]);
}
}
inline long long query(const long long &k){
if(V.size()!=n) --k;
if(k>=(1LL<<V.size())) return -1;
long long s=0;
for(int i=0;i<V.size();++i){
if((k>>i)&1)
s^=V[i];
}
return s;
}