线性基学习笔记
好厉害的东西。
线性基可以在 \(O(\log n)\) 的空间内,维护 \(n\) 个数的异或值。
线性基有如下性质:
- 原数列里的任何一个数都可以通过线性基里的数异或表示出来
- 线性基里任意一个子集的异或和都不为 \(0\)
- 一个数列可能有多个线性基,但是线性基里数的数量一定唯一,而且是满足性质一的基础上最少的
构造
\(p[i]\) 代表线性基第 \(i\) 位存的值。
插入一个数 \(x\),从高到低枚举每个二进制位。
- 如果当前位 \(x\) 值为 \(0\) ,跳过 先不管
- 如果当前位 \(x\) 值为 \(1\)
\(1.p[i]=0:\) 将 \(x\) 插入 \(p[i]=x\) 直接结束。
\(2.p[i]\not=0:\) \(x→x\ \text{xor}\ p[i]\)
可以发现 这样到最后 要么 \(x\) 被插入
要么到最后 \(x=0\) ,说明 \(x\) 可以被线性基中的元素表示出来
inl void insert(int x){
for(int i=52;(~i)&&x;i--){
if(x&(1ll<<i)){
if(!p[i]){p[i]=x;break;}
else x^=p[i];
}
}
}
查询
异或最值
最小值比较简单,答案就是线性基中最小的数,因为它异或其他值一定都会变大。
inl int query_min(){
for(int i=0;i<=52;i++)
if(p[i])return p[i];
return -1;
}
最大值直接贪心即可
从高到低枚举每一位,如果 \(ans\) 此时为 \(0\) 且 \(p[i]\not=0\) 那么选上 这一位一定变\(1\) 答案一定会变大
但其实不用这么麻烦 直接看异或之后答案是否变大即可。
inl int query_max(){
int ans=0;
for(int i=52;~i;i--)
ans=max(ans,ans^p[i]);
return ans;
}
是否存在几个数异或和为 \(x\)
如果 \(x\) 的这一位有值 就把这一位异或掉。
最后变0了就赢了。
inl bool check(int x){
for(int i=52;~i;i--)
if(x&(1ll<<i))x^=p[i];
return !x;
}
查询异或第 \(k\) 小/查询排名
首先 我们需要进行如下rebuild操作:
inl void rebuild(){
for(int i=52;~i;i--)
for(int j=i-1;~j;j--)
if(p[i]&(1ll<<j))p[i]^=p[j];
for(int i=52;~i;i--)
if(p[i])d[cnt++]=p[i];
}
不难发现 经过如下处理后 元素总数不会变 而每一位最多只有一个元素是 \(1\)
也就是说,线性基中每两个元素都是互不影响的
那么我们还是直接贪 由于所有小的合起来都比一个大的小 不难写出如下代码:
inl int kth(int k){
int ans=0;
for(int i=cnt-1;~i;i--)
if(k&(1ll<<i))ans^=d[i];
return ans;
}
inl int rank(int x){
int ans=0;
for(int i=cnt-1;~i;i--)
if(x>=d[i])ans+=(1ll<<i),x^=d[i];
return ans+zero;
}
对了注意没插入的元素会贡献0(

浙公网安备 33010602011771号