线性基
前置知识
对于若干向量组成的线性空间 \(V\),其存在子集 \(B\).
\(B\) 是线性无关的(指任意向量不能被其他向量通过线性组合表示)。
而且 \(B\) 的张成(其通过任意线性组合表示的所有向量)为 \(V\).
引入
OI 中的基,一般都运用于解决异或的问题。
两个二进制有 \(len\) 位的数异或,也就是两个 \(len\) 维向量线性组合。
若有序列 \(\{a_i\}\),值域是 \([1,m]\),其线性基就是不多于 $\left \lceil \log_2(m) \right \rceil $ 个数,通过异或的组合可以凑成序列的所有数。
这个线性基第 \(i\) 位的二进制最高位也是 \(i\).
构建
构建的其中一种是这样:
若我们插入一个数 \(x\),设最高位是 \(i\).
1.若当前线性基第 \(i\) 位有值 \(a_i\),那么再次插入 \((x\otimes a_i)\),直到 \(x=0\).
2.若 \(a_i\) 还没有值,那么就令 \(a_i=x\) 并退出。
可见插入一个数复杂度是 \(O(\log)\) 的。
若退出时 \(x\not =0\) ,就代表为了表示 \(x\),线性基新赋值了一个数。
反之,那么代表 \(x\) 已经被表示了。
笔者发现,若线性基已经满了,再插入的数,都是能够表示的。
所以此时线性基已经能表示 \(2^n-1\) 里的所有数了。
应用
是否能凑出某数
如同插入一个数那样,看结尾时 \(x\) 是否是 \(0\).
最大值
查询任意组合异或出的最大值。
从高到低位依次枚举,设当前到了线性基的第 \(i\) 位,
若线性基 \(a_i\) 有值:且当前答案的第 \(i\) 位还没有值,那么令答案异或上 \(a_i\)。反之,则跳过。
这样做每次答案都不减,故正确。
若查询加上 \(x\) 任意组合的最大值,令答案初始值为 \(x\) 即可。
code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=55;
LL n,a[N];
void ins(LL x) {
for(int i=54; i>=0; i--) {
if(x&(1ll<<i)) {
if(a[i]) x^=a[i];
else {a[i]=x; break;}
}
}
}
LL qmax() {
LL res=0;
for(int i=54; i>=0; i--) {
res=max(res,res^a[i]);
}
return res;
}
int main() {
scanf("%lld",&n);
for(int i=1; i<=n; i++) {
LL u; scanf("%lld",&u);
ins(u);
}
printf("%lld\n",qmax());
return 0;
}
注意代码中 1ll<<i
一定要写。
线性基合并
直接将其中一个插入另一个即可。复杂度 \(O(\log^2)\).