有一个序列 A A A,若存在一个序列 B B B,使得对于 A A A中任意若干个数的异或和 k k k,一定有 B B B中的若干个数,使得这些数的异或和为 k k k,且 B B B是满足以上条件的长度最小的序列,则称 B B B为 A A A的线性基。
线性基可以用来解决一些子集异或和的问题。
设数组 d d d为数组 a a a的线性基( d d d从 0 0 0开始),则我们可以得到 d d d的长度一定小于等于 a a a中的最大数的二进制的位数。为什么呢?如果 d i = 2 i d_i=2^i di=2i,则如果 d d d的长度与 a a a中最大数的位数,则 a a a中的每一个数都能按二进制位用 d i d_i di异或而得。
我们规定:若 d i d_i di不为 0 0 0, d i d_i di的最高位为 i + 1 i+1 i+1。当然, d i d_i di为 0 0 0表示 d i d_i di不存在。那么,我们可以构造数组 d d d。
for(int i=1;i<=n;i++){
for(int j=mx;j>=0;j--){
if((a[i]>>j)&1){
if(!d[j]){
d[j]=a[i];
break;
}
a[i]^=d[j];
}
}
}
对于每个 a i a_i ai,设 a i a_i ai的最高位为 k k k,若 d k − 1 = 0 d_{k-1}=0 dk−1=0,则将 a i a_i ai赋给 d k − 1 d_{k-1} dk−1,否则令 a i = a i ⊕ d k − 1 a_i=a_i\oplus d_{k-1} ai=ai⊕dk−1。到最后一定能有若干个数能构成后来的 a i a_i ai,那么用 a i ⊕ d k − 1 a_i\oplus d_{k-1} ai⊕dk−1就能得到先前的 a i a_i ai。
注:异或运算满足 a ⊕ b ⊕ b = a a\oplus b\oplus b=a a⊕b⊕b=a
有 n n n个数组成的一个可重集合 S S S,求一个集合 T ⊆ S T\subseteq S T⊆S,使得 T 1 ⊕ T 2 ⊕ T 3 ⊕ ⋯ ⊕ T ∣ T ∣ T_1 \oplus T_2 \oplus T_3 \oplus \cdots \oplus T_{|T|} T1⊕T2⊕T3⊕⋯⊕T∣T∣最大。
构造 S S S的线性基 d d d,显然 S S S的最大异或和一定可以用 d d d中的若干个数的异或和得到。
令答案为 a n s ans ans, a n s ans ans的初始值为 0 0 0。我们按 d d d的下标从大到小遍历。对于每个 d i d_i di,若 a n s ⊕ d i > a n s ans\oplus d_i>ans ans⊕di>ans,则 a n s = a n s ⊕ d i ans=ans\oplus d_i ans=ans⊕di。为什么呢?在 i + 1 i+1 i+1位之前的的每一位不变的情况下,第 i + 1 i+1 i+1位如果是 0 0 0,则将其变为 1 1 1一定是更优的。因为在之后各个 a i a_i ai的第 i + 1 i+1 i+1位一定为 0 0 0,所以只有在这个数中才能将 a n s ans ans的这一位变为 1 1 1。
所以我们可以通过 a n s ⊕ d i ans\oplus d_i ans⊕di和 a n s ans ans的大小关系来决定是否要异或 d i d_i di。
#include
using namespace std;
int n;
long long ans=0,a[100005],d[55];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=50;j>=0;j--){
if(a[i]&(1ll<<j)){
if(!d[j]){
d[j]=a[i];
break;
}
a[i]^=d[j];
}
}
}
for(int i=50;i>=0;i--){
if((ans^d[i])>ans) ans=ans^d[i];
}
printf("%lld",ans);
return 0;
}