线性基学习笔记
定义
张成
我们定义集合\(S \subseteq \mathbb{N^*}\). \(T\)为\(S\)的任意子集, 所有\(T\)的异或和组成的集合称为\(S\)的张成, 写作\(span(S)\).
线性相关与线性无关
假如集合\(S\)中存在这样一个元素, 我们将其从\(S\)中去掉后得到的集合为\(S'\)有\(span(S') = span(S)\), 那么我们称集合\(S\)是线性相关的; 假如不存在这样的一个元素, 则称集合\(S\)线性无关.
线性基
我们称线性无关的集合\(B\)是集合\(S\)的线性基, 当且仅当\(S \subseteq span(B)\), 且对于\(B\)的任意真子集\(B'\)都不满足\(s \subseteq span(B')\).
\(B\)中的元素个数称为线性基的长度.
性质: \(S\)中的任意元素都可以唯一地表示为\(B\)中若干个元素的异或和.
证明:
反证法. 假设我们可以通过两种方式得到\(S\)中的某个元素, 也就是说
那么就有
显然不满足定义中线性无关的性质, 不成立. 因而命题得证.
线性基的构造
鉴于当前没有太多关于线性基的论文可以参考, 下文中可能有许多不严谨之处.
首先考虑线性基应该是什么样子的: 这与我们构造线性基的目的有关. 线性基通常被用于解决最大异或和/k大异或和问题, 因此它一般会被构建成这样:
我们设\(S\)中最大元素在二进制下有\(L\)位, 则我们用一个编号为\([0 ... L - 1]\)的数组来存储\(S\)的线性基. 这个数组的含义可以被理解作一种独立和捆绑的关系, 表示哪些位上的\(0\)或\(1\)被捆绑在一起出现, 哪些位上的\(0\)或\(1\)互不影响.
具体来说, 数组上某一位的数假如为\(0\), 则这一位不能通过异或得到, 或者是这一位与某一更高位捆绑出现; 而假如某一位的数不为\(0\), 则这一位与更高位相互独立; 同时, 根据这个数的二进制表示, 这一位可能与更低位存在捆绑关系. 不难看出, 一个数位只能存在捆绑和独立中的一种状态, 因此二进制中每一位只能在线性基中出现一次. 比方说, 我们假设这个数组第\(2\)位上的数为\(5\), 因为\(5\)不为零, 因此说明这一位相对于更高位独立存在; 与此同时, 由于\(5\)的二进制表示为\(101\), 因此第\(2\)位的数字与更低位的第\(0\)位存在捆绑关系.
明确了线性基的形态, 构建方法就变得显而易见了. 当我们要往线性基中插入一个数\(x\)时:
- 在线性基中从高位到低位逐位比较. 我们令当前位为\(p\), 假如\(x\)的第\(p\)位为\(1\), 则进入下列讨论:
- 假如线性基的第\(p\)位不为\(0\), 则\(x\)异或上线性基中这一位的数. 原因: \(x\)通过与线性基中原有的数进行异或, 可以使得当前位和后面的位脱离捆绑关系.
- 假如线性基的第\(p\)位为\(0\), 则我们从第\(0\)位到第\(p - 1\)位进行枚举, 假如线性基中的这一位不为\(0\)并且\(x\)的这一位也不为\(0\), 则\(x\)要异或上线性基中的这个数. 原因: 一个位不可能同时存在捆绑关系与独立关系, 因此我们要消去第\(p\)位与这一位的捆绑关系. 我们再从第\(p + 1\)位到最高位进行枚举, 假如线性基中这一位的数中, 与\(x\)的最高位存在捆绑关系, 则把这个数异或上\(x\). 原因: 还是一样的.
代码
最大异或和
#include <cstdio>
#include <cctype>
namespace Zeonfai
{
inline long long getInt()
{
long long a = 0, sgn = 1;
char c;
while(! isdigit(c = getchar())) if(c == '-') sgn *= -1;
while(isdigit(c)) a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
}
struct linearBasis
{
long long a[51];
inline void insert(long long x)
{
for(int i = 50; ~ i; -- i)
if(x >> i & 1)
{
if(! a[i])
{
for(int j = 0; j < i; ++ j) if(a[j] && x >> j & 1) x ^= a[j];;
a[i] = x;
for(int j = 50; j > i; -- j) if(a[j] >> i & 1) a[j] ^= x;
break;
}
else x ^= a[i];
}
}
inline long long getAnswer()
{
long long res = 0;
for(int i = 50; ~ i; -- i) res ^= a[i];
return res;
}
}LB;
int main()
{
#ifndef ONLINE_JUDGE
freopen("linearBasis.in", "r", stdin);
freopen("linearBasis.out", "w", stdout);
#endif
using namespace Zeonfai;
int n = getInt();
for(int i = 0; i < n; ++ i) LB.insert(getInt());
printf("%lld", LB.getAnswer());
}
第K小异或和
#include <cstdio>
#include <cctype>
namespace Zeonfai
{
inline long long getInt()
{
long long a = 0, sgn = 1;
char c;
while(! isdigit(c = getchar())) if(c == '-') sgn *= -1;
while(isdigit(c)) a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
}
int flg = 0;
struct linearBasis
{
long long a[51];
inline void insert(long long x)
{
for(int i = 50; ~ i; -- i)
if(x >> i & 1)
{
if(! a[i])
{
for(int j = i - 1; ~ j; -- j) if(a[j] && x >> j & 1) x ^= a[j];;
a[i] = x;
for(int j = 50; j > i; -- j) if(a[j] >> i & 1) a[j] ^= x;
return;
}
else x ^= a[i];
}
flg = 1;
}
}LB;
int main()
{
#ifndef ONLINE_JUDGE
freopen("linearBasis.in", "r", stdin);
freopen("linearBasis.out", "w", stdout);
#endif
using namespace Zeonfai;
int n = getInt();
for(int i = 0; i < n; ++ i) LB.insert(getInt());
int ext[50], cnt = 0;
for(int i = 0; i <= 50; ++ i) if(LB.a[i]) ext[cnt ++] = i;
long long sum = (long long)1 << cnt;
int m = getInt();
for(int i = 0; i < m; ++ i)
{
long long k = getInt() - flg;
if(k >= sum) {puts("-1"); continue;}
long long res = 0;
for(int j = 0; j < cnt; ++ j) if(k >> j & 1) res ^= LB.a[ext[j]];
printf("%lld\n", res);
}
}