前言
可以将线性基理解为是将一个序列处理完之后得到的产物。线性基是一个集合,在原集合中找到一个子集,子集中的数异或起来一定能在线性基中找一个对应子集的异或和与其相等。
比如说,{x,y} 和 {x, x^y} 就满足这样一个关系。
性质
- 原序列里面的任意一个数都有唯一方案由线性基中元素异或得到。
- 在线性基中任取若干个元素,它们的异或值都不为零。即它们线性无关。
- 线性基的大小唯一且最小
构造
考虑如何在已有一组线性基的情况下,向线性空间的元素集合中插入一个元素。
插入新的元素后,需要满足:
- 线性基张成的空间中包含新插入的元素。
- 线性基仍然线性无关。
具体地,依次考虑新插入元素的每个为1的二进制位,若线性基不存在这一位,那么将这个新元素加入线性基中。
否则,将新元素异或上线性基的这一位,然后继续处理下一位。
因为插入一个元素等同于插入其异或上线性基中的一个元素,所以性质一满足。
从构造过程中就可以看出,已有线性基无法异或得到这个新元素,所以性质二满足。
线性基中的每个元素的二进制最高位均不同。
for(int i = 1; i <= n; i++) {
for(int j = 60; j >= 0; j--) {
if(a[i] & (1LL << j)) { // 1后面必须要加longlong,否则会爆
if(!f[i]) {
f[i] = a[i]; // f[]即为线性基
break;
}
a[i] ^= f[i];
}
}
}
性质证明
首先,要了解异或运算具有如下性质:
- a ^ b ^ c = 0,那么a ^ b = c
- 如果a ^ b = c,那么a ^ c = b
性质一
原序列里面的任意一个数都有唯一方案由线性基中元素异或得到
设原序列里面有一个数x,尝试用它来构造线性基,那么会有两种结果:
1、不能成功插入线性基;2、成功插入线性基。
如果不能成功插入线性基
当一个数不能插入时,显然是尝试插入它时异或若干个数之后变成了0。
那么就有如下式子: x ⊕ f [ a ] ⊕ f [ b ] ⊕ f [ c ] ⊕ . . . = 0 x \oplus f[a] \oplus f[b] \oplus f[c] \oplus ... = 0 x⊕f[a]⊕f[b]⊕f[c]⊕...=0
根据异或运算的性质则有: f [ a ] ⊕ f [ b ] ⊕ f [ c ] ⊕ . . . = x f[a] \oplus f[b] \oplus f[c] \oplus ... = x f[a]⊕f[b]⊕f[c]⊕...=x
所以,如果x不能成功插入线性基,一定是因为当前线性基里面的一些数异或起来已经可以等于x。
如果可以成功插入线性基
假设x插入到了线性基的第i个位置,那么有:
x ⊕ f [ a ] ⊕ f [ b ] ⊕ f [ c ] ⊕ … = f [ i ] x \oplus f[a] \oplus f[b] \oplus f[c] \oplus … = f[i] x⊕f[a]⊕f[b]⊕f[c]⊕…=f[i]
f [ i ] ⊕ f [ a ] ⊕ f [ b ] ⊕ f [ c ] ⊕ … = x f[i] \oplus f[a] \oplus f[b] \oplus f[c] \oplus … = x f[i]⊕f[a]⊕f[b]⊕f[c]⊕…=x
所以显然,x 此时也可以由线性基里面的若干个数异或得到。
性质二
在线性基中任取若干个元素,它们的异或不为零。即它们线性无关。
使用反证法
设 f [ a ] ⊕ f [ b ] ⊕ f [ c ] = 0 f[a] \oplus f[b] \oplus f[c] = 0 f[a]⊕f[b]⊕f[c]=0(其中 f[c] 比 f[a] 和 f[b] 要晚插入线性基)
那么有 f [ a ] ⊕ f [ b ] = f [ c ] f[a] \oplus f[b] = f[c] f[a]⊕f[b]=f[c]
所以 f[c] 可以由 f [ a ] ⊕ f [ b ] f[a] \oplus f[b] f[a]⊕f[b]得到,所以 f[c] 不可能插入线性基
故假设不成立,所以线性基中不存在有任何数异或起来可以得到0。
性质三
线性基的空间唯一,并且空间最小
假如原序列的所有元素都可以插入到线性基里面
如果是这种情况的话,不管是用什么顺序将序列里的数插入到线性基里,线性基中的元素一定与原序列元素数量相同。所以性质3成立。
假如原序列的一些元素不能插入到线性基里面
假设x不能插入到线性基里面,那么一定满足形如 f[a] ^ f[b] ^ f[c] = x
的式子
那我们尝试将插入顺序改变,变成:f[a]、f[b] 、x、f[c] 。
那么显然,d[c]是不可能插入成功的,因为 f[c] 已经可以由 f[a] ^ f[b] ^ x
得到
原来是x 插入不进去,改变顺序后,f[c] 插入不进去,也就是说,对于插入不进去的元素,改变插入顺序后,要么还是插入不进去,要么就是插入进去了,同时另一个原来插入的进去的元素插不进去了,所以,可以插入进去的元素数量一定是固定的。
如果去掉线性基里面的任意一个数,都会使得原序列里的一些数无法通过用线性基里的元素异或得到,所以,每一个元素都是必要的,换句话说,线性基里面没有多余的元素。
线性基的应用
询问一个数能否表示为数组中的某些数的异或和
先求出线性基,从高位开始依次操作,如果当前位为1,那么就让x异或线性基中当前位为1的数,否则不操作。最后判断结果是否为0。(一个数在异或后变成了0,肯定是因为这个数异或上了它自己)
求序列的最大/最小异或和
首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为0。
LL ans = 0LL;
for(int i = 60; i >= 0; i--) // 从线性基的最高位开始
ans = max(ans, ans ^ f[i]);
本质是一个贪心的求解过程,优先使最高位尽可能大,因为当前位来说,后面的低位无论有多少个1,贡献也没有当前位大
对于序列的最小异或和来说,答案显然就是最小的 f[i]
求第k小异或和(HDU 3949)
对于一般的线性基来说
1000001
0000001
同时选1和2比只选1要差,所以一般的线性基无法做。
但是如果线性基是下面这种形式:
100000
010000
001000
000100
那么就可做了,因为选取1和2一定比只选1要优。
所以需要对原来的线性基重构一下,保证线性基中的每一位,只有一个数可以使当前位为1,其他数的当前位都为0.
首先根据性质一得到的基,最高位1的位置互不相同。 记 a i a_i ai 为 线性基中最高位的1在第 i 位的 向量, 那么按照 i 从大到小的顺序, 用 a i a_i ai 去异或 a j ( j > i ) a_j (j > i) aj(j>i) .
对于每个 f[i] 来说,枚举 j = 0 ~ i - 1
,如果 f[i] 的第 j 位为1,就让 f[i] ^ f[j]
可以发现,处理完之后,只有 a i a_i ai 的第 i 位是1,其他的第 i 位都是0。线性基中的元素,作用其实都是提供自己最高位上的1。
把 k二进制拆分,每一位的 0/1
对应异或时 选/不选
线性基存在的这一位,若 k 的第 i 位为1,那么 ans 就异或上线性基中第 i 个元素(注意不是异或 f[i-1])。
线性基中存在的位的 0/1
唯一确定了一个异或出的数,由于每个位只在一个基中为 1,所以这些位组成的二进制数的大小就可以代表异或出的数的大小。
需要特别注意的是: 0能不能被异或出来的问题。线性基中只能异或出不为0的解,如果线性基的大小和原数组一样,0是不能被异或出来的,否则可以。
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)
typedef long long LL;
LL a[MaxN], f[70];
int n, m, cnt = 0;
void guass() {
for(int i = 62; i >= 0; i--) f[i] = 0LL;
for(int i = 1; i <= n; i++) {
for(int j = 62; j >= 0; j--) {
if((a[i] >> j) & 1) {
if(!f[j]) {
f[j] = a[i];
break;
}
a[i] ^= f[j];
}
}
}
for(int i = 62; i >= 0; i--) {
for(int j = i + 1; j <= 62; j++)
if((f[j] >> i) & 1) f[j] ^= f[i]; // 只有f[i]的第i位为1
}
cnt = 0;
for(int i = 0; i <= 62; i++)
if(f[i]) f[cnt++] = f[i];
}
LL getKth(LL k) {
if(cnt < n) { // 说明原序列可以异或出0
if(k == 1) return 0; // 直接返回最小值0
else k--; // 去掉0这个最小值后的第K小
}
if(k >= (1LL << cnt)) return -1;
LL ans = 0LL;
for(int i = 0; i <= 62; i++) {
if((k >> i) & 1) ans ^= f[i];
}
return ans;
}
int main()
{
rush() {
printf("Case #%d:\n", cas);
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", a + i);
guass();
scanf("%d", &m);
while(m--) {
LL k; scanf("%lld", &k);
printf("%lld\n", getKth(k));
}
}
return 0;
}
其他好题
BZOJ 2115
codeforces724G Xor-matic Number of the Graph
BJWC 2011 元素
SCOI 2016 幸运数字
TJOI 2008 彩灯