线性基入门
线性基入门
线性基用来解决竞赛中关于子集异或的一类问题。
1 定义
-
异或和
设 \(S\) 为无符号整数集,(若不说明,下文所有集合均指无符号)那么集合 \(S\) 的异或和是 \(xor\_sum(S)=s_1\hat{}s_2...s_{|S|}\)
-
张成
设 \(T\subseteq S\) ,则所有的 \(sor\_sum(T)\) 组成的子集称为 \(S\) 的张成,记作 \(span(S)\)
-
线性相关
如果存在一个 \(S_j\) ,用 \(S\) 中其他的一些元素可以异或得到,或者等价的说,\(S_j\in span(S-S_j)\) ,那么称 \(S\) 线性相关,否则线性无关。
-
我们称集合 \(B\) 为 \(S\) 的线性基当且仅当满足:
- \(S\subseteq span(B)\) ,即 \(S\) 是 \(B\) 张成的子集。
- \(B\) 线性无关。
- \(B\) 是极小的满足上述性质的集合。
2 性质
- 线性基的任何真子集都不可能是线性基。
- \(S\) 中的任意元素都可以唯一的表示为 \(B\) 中若干元素异或起来的结果。
3 构造
问题是给定一个子集,要求求其线性基,下面给出构造代码。
ll a[60];
inline void insert(ll x){
for(int i=55;i>=0;i--){
if(!(x&(1ll<<i))) continue;
if(!a[i]){a[i]=x;break;}else x^=a[i];
}
}
如果 \(a_i\) 不为 \(0\) ,那么 \(a_i\) 上的数的二进制最高位必定为第 \(i\) 位,这个性质说明了这个线性基线性无关。
显然,异或运算是可逆的,重新异或一遍就可以得到原来的数,所以这个线性基的长成子集包含 \(S\) 。
综上,可以知道上述构造方案是正确的。
还有一种构造方案可以使构造出的线性基有一个特殊的性质:
- 如果 \(a_i\) 不为 \(0\) ,那么除了 \(i\) 之外的所有 \(j\) 满足 \(a_j\) 第 \(i\) 位不为 \(1\)。
我们只需要在加入的时候枚举 \(j<i\) 的所有 \(a_j\) ,用 \(x\) 去异或( \(x\) 是我们插入的数)即可满足 \(x\) 的第 \(j\) 位没有这些数。同时我们枚举 \(j>i\) 的所有 \(a_j\) ,用他们异或 \(x\) ,即可满足后面的数不会有 \(x\) 。
代码需要判段该位是否为 \(1\) 。
代码:
ll a[60];
inline void insert(ll x){
for(int i=55;i>=0;i--){
if(!(x&(1ll<<i))) continue;
if(!a[i]){
for(int j=i-1;j>=0;j--) if(x&(1ll<<j)) x^=a[j];
for(int j=i+1;j<=55;j++) if(a[j]&(1ll<<i)) a[j]^=x;
a[i]=x;break;
}else x^=a[i];
}
}
注意:第 \(5,6\) 行不能交换,因为在加入 \(x\) 前线性基满足性质,如果交换顺序,很可能在让 \(a_j\) 异或 \(x\) 的时候让 \(a_j\) 的某一位变成一从而不满足性质,所以要先改变 \(x\) 。
算法的正确性依然是根据异或的可逆性。
明显上面的两个构造方法单次插入都是 \(\log n\) 的。
4 求最大值
对于第一个构造方法,我们需要贪心的去求解最大值:如果异或变大就异或,因为优先异或位数大的肯定要更优。
对于第二种,因为其特殊性质,我们直接异或到底。
代码 \(1\) :
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N number
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
struct xianxingji{
ll a[60];
inline void insert(ll x){
for(int i=55;i>=0;i--){
if(!(x&(1ll<<i))) continue;
if(!a[i]){a[i]=x;break;}else x^=a[i];
}
}
inline ll ask_max(){
ll ans=0;
for(int i=55;i>=0;i--){
if((ans^a[i])>ans) ans=ans^a[i];
}
return ans;
}
};
xianxingji xxj;
int n;
int main(){
read(n);
for(int i=1;i<=n;i++){
ll x;read(x);xxj.insert(x);
}
printf("%lld\n",xxj.ask_max());
return 0;
}
代码 \(2\) :
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N number
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
struct xianxingji{
ll a[60];
inline void insert(ll x){
for(int i=55;i>=0;i--){
if(!(x&(1ll<<i))) continue;
if(!a[i]){a[i]=x;break;}else x^=a[i];
}
}
inline ll ask_max(){
ll ans=0;
for(int i=55;i>=0;i--){
if((ans^a[i])>ans) ans=ans^a[i];
}
return ans;
}
};
xianxingji xxj;
int n;
int main(){
read(n);
for(int i=1;i<=n;i++){
ll x;read(x);xxj.insert(x);
}
printf("%lld\n",xxj.ask_max());
return 0;
}