线性基学习笔记
线性基是什么
集合\(S\)有子集\(T\)
使得\(S\)的任意子集的元素的亦或和
都能被\(T\)的某个子集的元素亦或得到
且\(T\)的任何子集的亦或和不为\(0\)
\(T\)就是\(S\)的线性基
显然\(S\)的子集亦或和的值域与\(T\)的子集亦或和的值域相等
比如说\(S=\{1,10,11,100,101,110\}\) (二进制)
那么\(T\)就是\(\{1,10,100\}\)
怎么求线性基
高斯消元
高斯消元可以看做求一组n阶向量的线性基的过程
最后求出的矩阵的秩,就是线性基的元素个数
消完元后的线性无关组,就是一组线性基
求\(xor\)线性基与此类似,只是用一个\(long long\)来代替高斯消元的\(n\)阶向量
高斯消元最后保留的是一个类似上三角矩阵的东西,
与此同理,
\(xor\)线性基中,每一个二进制位,在线性基数组中只保留最多一个最高位在该位上的数
\(S=\{1,10,11,100,101,110\}\) (二进制)
\(T=\{1,10,100\}\)
如上面的例子,最高位在0位上的只有1,在1位上的只有10,在2位上的只有100
具体求法:
依次将数插入到线性基,
如果该数的最高位1上对应的线性基元素为0,
那么就将这个数作为该位的线性基元素,插入成功,退出
否则将这个数亦或该位上的线性基元素,
这时这个数的最高位1变小,就继续往下找
直至该数插入成功
或者被亦或成了0,说明这个数可以被其他几个数的亦或和表示出来,插入失败
代码
#define LL long long
LL p[MAXN]; //最高位1在第i位上的线性基元素,如果没有,就为0
inline void ins(LL x){
for(int i=Maxlog;i>=0;--i)
if(x&(1ll<<i)){
if(!p[i]){
p[i]=x;
return;
}
x^=p[i];
if(!x) return;
}
}
应用
一、求最大亦或和
知道一组数的线性基以后,我们可以用它求最大的子集的元素亦或和
我们知道,一个集合中所有的子集元素的亦或和都可以由线性基的子集元素的亦或和求出
所以问题转化为求线性基的子集元素的最大亦或和
因为一组线性基每个位上只有最多一个对应的数,我们就可以利用这个性质贪心选取
\(ans\)初值为\(0\)
从最高位到最低位枚举有数的位\((p[i]!=0)\),
如果\(ans\)在当前位的值为\(0\),\(ans^=p[i]\),
\(ans\)的当前位从\(0\)变为了\(1\),这显然比不亦或p[i]的任何情况更优
判断\(ans\)的当前位是否为\(0\),可以用 \(if(ans<(ans~xor~p[i]))\),显然没有问题
代码
inline LL Get_Max(){
LL ans=0;
for(int i=Maxlog;i>=0;--i)
if(ans<(ans^p[i])) ans^=p[i];
return ans;
}
二、查询某个数是否在亦或和的值域集合内
如果能成功插入这个数,就不在值域集合内,
否则就在值域集合内
inline bool check(LL x){
for(int i=Maxlog;i>=0;--i)
if(x&(1ll<<i)){
if(!p[i]) return 0;
x^=p[i];
if(!x) return 1;
}
}
三、求亦或和值域中的k小值
首先,求线性基
之后,我们将每个\(p[i]\)搞成最小的,即保证\(p[i]\)在\(i\)位以下的\(p[j]!=0\)的数位\(j\)上都为\(0\)
和上面求最大值的方法类似,枚举\(i-1~0\)位的\(p[j]\),如果\((p[i]>p[i]^p[j])\),\(p[i]^=p[j]\)
我们可以意会一下,求第\(k\)小值,
对于在\(k\)的二进制中的数位\(j\),\(k\)的二进制在\(j\)位为\(1\)
我们就把\(p\)数组中不为零的排名为\(j\)数的亦或到\(ans\)上即可
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#define LL long long
using namespace std;
const int MAXN=10010;
const int Maxlog=61;
inline LL read(){
LL x=0; char c=getchar();
while(c<'0') c=getchar();
while(c>='0') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x;
}
int T,n,m;
LL p[MAXN];
bool flag;
inline void ins(LL x){ //插入一个数x
for(int i=Maxlog;i>=0;--i)
if(x&(1ll<<i)){
if(!p[i]){
p[i]=x;
return;
}
x^=p[i];
if(!x){
flag=1; //这道题中需要考虑0,flag表示0是否能被一个非空子集亦或出来
return;
}
}
}
int main()
{
T=read();
for(int i=1;i<=T;++i){
memset(p,0,sizeof(p));
printf("Case #%d:\n",i);
n=read();
flag=0;
LL x;
while(n--){
x=read();
ins(x);
}
for(int i=Maxlog;i>=0;--i)
if(p[i])
for(int j=i-1;j>=0;--j)
if(p[i]>(p[i]^p[j]))
p[i]^=p[j];
int cnt=-1;
for(int i=0;i<=Maxlog;++i)
if(p[i]) p[++cnt]=p[i]; //将不为0的p[i]排名
m=read();
while(m--){
x=read();
if(flag) --x; //0占了一个名次
if(x>=(1ll<<cnt+1)){
puts("-1");
continue;
}
LL ans=0;
for(int i=0;i<=Maxlog;++i)
if(x&(1ll<<i)) ans^=p[i];
printf("%lld\n",ans);
}
}
return 0;
}
以上只是线性基应用的几个例子,只要明白了原理,我们可以用线性基解决很多问题
练习题