线性基学习笔记
线性基的定义
在一个高维空间中一组极大的线性无关的向量组成为一组线性基,更严谨的定义参考线性代数相关内容。
但是在 OI 中我们常用的是异或线性基,它维护了给定若干个数能够通过异或计算出的所有的数,具体来说可以实现以下几个功能:
- 求 min/max 异或和
- 求 k 大异或和
- 求异或和数量
线性基的构建
分为高斯消元法和贪心法,这里介绍贪心法。
考虑插入一个数 \(x\),从高位到低位遍历一个线性基,如果当前位线性基内没有值,那么把 \(x\) 作为当前位的代表值,否则让 \(x\) 异或上这一位,继续插入。
考虑这样做的正确性,从低位到高位插入,如果当前位没有值,那么 \(x\) 必不可能被表示,反之则 \(x\) 的这一位可有可无,如果一直到最后 \(x\) 都没有被插入,则我们认为本次插入失败。
需要注意的是,我们构建出来的只是一个能实现线性基功能的东西而不是严格的线性基。
code:
bool insert(int x){
for(int i = 60; i >= 0; --i){
if(!(x & (1ll << i))) continue;
if(p[i]) x ^= p[i];
else {p[i] = x; return 1;}
}
return 0;
}
求最大/最小异或和
max:继续考虑贪心,我们从高位到低位遍历,如果 \(res\) 这一位为 \(0\),那么让 \(res\) 异或上这一位。
正确性可以由线性基的性质推知,如果某一位上的 \(1\) 在当前出现了,那么之后必不可能出现,而我们是从高位到低位遍历的,那么每次遇到即取肯定是没问题的。
最小值同理,线性基中最小的一个即为答案,但是如果一个数插入失败,说明线性基可以表出 \(0\),那么最小值应该是 \(0\).
求 k 大异或和
假设我们现在有一组严格线性基 \(\{p_n,p_{n-1}....p_0\}\),如果能够表示出 \(0\) 那么 \(0\) 是最小值,否则最小值是 \(p_0\),次小值是 \(p_1\),再次小值是 \(p_1\ xor\ p_0\),如此一来,发现这个过程和二进制的表示惊人的相似,所以我们先将线性基严格化,使得线性基中所有元素没有重复的 \(1\),那么所求值即为 \(k\) 的二进制位为 \(1\) 的位置的线性基的异或和。
LOJ114
code:
点击查看代码
#include <bits/stdc++.h>
#define AC true
#define int long long
#define dub double
#define mar(x) for(int i = head[x]; i; i = e[i].nxt)
#define car(a) memset(a, 0, sizeof(a))
#define cap(a, b) memcpy(a, b, sizeof(b))
const int inf = 1e9 + 7;
const int MAXN = 3e6;
const dub eps = 1e-6;
using namespace std;
inline int read( ){
int x = 0 ; short w = 0 ; char ch = 0;
while( !isdigit(ch) ) { w|=ch=='-';ch=getchar();}
while( isdigit(ch) ) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return w ? -x : x;
}
int p[100];
int bit[100], tot;
void insert(int x){
for(int i = 60; i >= 0; --i)
if(x & (1ll << i)){
if(p[i]) x ^= p[i];
else {p[i] = x; break;}
}
return;
}
void stand( ){
for(int i = 0; i <= 60; ++i)
for(int j = i - 1; j >= 0; --j)
if(p[i] & (1ll << j)) p[i] ^= p[j];
for(int i = 0; i <= 60; ++i)
if(p[i]) bit[tot++] = i;
}
signed main( ){
int n = read( );
for(int i = 1; i <= n; ++i){
int x = read( );
insert(x);
}
stand( );
int m = read( );
while(m--){
int k = read( ), ans = 0;
if(n > tot) k--;
if(k >= (1ll << tot)){printf("-1\n");continue;}
for(int i = 0; i < tot; ++i)
if(k & (1ll << i)) ans ^= p[bit[i]];
printf("%lld\n",ans);
}
return (0-0);
}