前言
这玩意以前听说过,然鹅一直木有学。
现在遇到题目要搞线性基了,然后就怒补了一番。
其实任何证明都不知道,(只会感性理解)就记记结论好了。
多项式全家桶一边呆着去
介绍
首先学习过线性代数的同学可能对基这个概念比较了解。
百度百科:
在线性代数中,基(也称为基底)是描述、刻画向量空间的基本工具。向量空间的基是它的一个特殊的子集,基的元素称为基向量。向量空间中任意一个元素,都可以唯一地表示成基向量的线性组合。如果基中元素个数有限,就称向量空间为有限维向量空间,将元素的个数称作向量空间的维数。
好吧,其实基就可以理解成一个坐标系的x轴y轴之类的。
而线性基则在OI界中的定义大为不同,它的基不是向量,而是记录的一个数。
简单理解线性基就是一个新构造的数组,然后这个数组记录的东东则是一些关于原数组元素一些异或操作的结果。怎么记录则是要从它的构造来看它的性质,从而得到这些东东的值。
构造
线性基的构造其实是每次插入一个数更新线性基从而构造出来的,而这个数组每一位记录的元素为当前位的数。
那么考虑现在的线性基是一个数组(a[i]),然后现在要插入一个数(x)。有两种情况:
- (a[i]=0)那么(a[i]=x)
- (a[i]!=0)那么(x=x xor a[i]),然后继续把(x)往下一位做。
所以就有代码:(简短清新)
void insert(long long x)
{
for (int i=62;i>=0;i--)
{
if (x&(mi[i]))
{
if (a[i])
{
x^=a[i];
}
else
{
a[i]=x;
break;
}
}
if (x==0)
{
flag0=true;
break;
}
}
}
性质
有好多性质,就瞎列几个:
- 原数列中相互异或得到的值,都可以在线性基中某些元素相互异或的值的集合中找到。(或者说,任意一个原数组中的元素,都可以在线性基中某些元素相互异或的值的集合中找到)
- 线性基是满足上述性质最小的集合。(这里最小指集合大小)
- 线性基中随意取出几个数异或出来的结果都互不相同。
一些结论、用法
得到上面三个比较重要的性质之后,我们可以得到某些比较有用的结论了。
- 1、 求异或集合的大小:(2^{基的个数})
- 2、 求异或集合的最大值:从高位往低位走。若当前值异或进答案使答案变大,就异或之。证明?由于当前这个高位的数不会被后面的数影响,所以可以贪心。
- 3、 求异或集合的最小值:直接取出最小的位置中不为0的元素。
- 4、 合并两个线性基:考虑合并(A)和(B),直接把(A)中的每个不为0的数插入(B)中即可。
- 5、 求异或集合的第k小值:这个有点♂骚。考虑更改一下做线性基时的步骤:当(a[i]=0)时,用(a[0-(i-1)])的数把(a[i])的(2^{0-(n-i+1)})位消去,再用x消去(a[(i+1)-n])的(2^i)位。这个过程类似于高斯消元。
- 做完当前这一步之后,线性基就变成一个由若干个(2^i)表示成的数,那么可以用这个来表示数,这样好搞。然后把k拆成二进制位,在线性基上匹配即可。(要特判0)
- 简单证明这样为什么是对的:考虑原来的构建方案,可以知道,不消去地构建这个数则保证了第i位可以为1,其他的数不会影响之。而消去之后依然能够保证,所以这样做是正确的。(其实消去版的线性基也可以维护前4条中的做法)
- 6、 求某个数能否被表示出来:同上述法。
附上消去版线性基插入:
void insert(long long x)
{
for (int i=62;i>=0;i--)
{
if (x&(mi[i]))
{
if (a[i])
{
x^=a[i];
}
else
{
a[i]=x;
cnt++;
for (int j=0;j<=i-1;j++)
{
if ((a[i] >> j) & 1) a[i]=a[i]^a[j];
}
for (int j=i+1;j<=62;j++)
{
if ((a[j] >> j) & 1) a[j]=a[j]^a[i];
}
break;
}
}
if (x==0)
{
flag0=true;
break;
}
}
}
学习资料:
百度百科
Cold_Chair's blog
https://oi-wiki.org/math/basis/
https://www.cnblogs.com/knife-rose/p/12372175.html
https://blog.csdn.net/qcwlmqy/article/details/97156192
https://www.cnblogs.com/yangsongyi/p/10692292.html
例题:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int n;
long long a[64],b[64],mi[64];
bool flag0;
void insert(long long x)
{
for (int i=62;i>=0;i--)
{
if (x&(mi[i]))
{
if (a[i])
{
x^=a[i];
}
else
{
a[i]=x;
break;
}
}
if (x==0)
{
flag0=true;
break;
}
}
}
int main()
{
mi[0]=1;
for (int i=1;i<=62;i++)
{
mi[i]=mi[i-1]*2;
}
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&b[i]);
insert(b[i]);
}
long long ans=0;
for (int i=62;i>=0;i--)
{
if ((ans^a[i])>ans)
{
ans=ans^a[i];
}
}
printf("%lld
",ans);
}