线性基 学习笔记
实际上就是通过一个可重集 \(A\) 来生成另一个不可重集 \(B\),使得 \(B\) 满足一些性质。
有点类似于有一些向量,找出能表示所有这些向量的基底向量。
本文主要涉及的是异或线性基。
1. 线性基的性质
性质一
\(A\) 中任意数都可以通过 \(B\) 中若干个数异或得到。
性质二
\(B\) 集合中不存在两个子集异或和一样。
性质三
\(|B|\) 是确定的,且在保证性质一的情况下是最小的。
性质四
对于 \(B\) 中任意子集, 存在 \(A\) 的子集与它异或和相等。
性质的证明
我们在将 \(A\) 中一个元素 \(w\) 加入时,若 \(B\) 中存在一个子集的异或和 与 \(w\) 相等时,则不加入 \(w\)。
那么这样我们就可以保证性质二。
考虑反证:
若 \(B\) 中存在子集 \(\{x_1,x_2,...,x_k\}\) 与 \(\{y_1,y_2,...,y_m\}\) 满足 \(\text{xor}_{i=1}^k\ x_i=\text{xor}_{i=1}^m\ y_i\)。我们就可以将最晚加入的数 (设为 \(x_1\))放在等号一侧,将剩下的全部放在等式右侧,则有 \(x_1=\text{xor}_{i=2}^k\ x_i\ \text{xor}\ \text{xor}_{i=1}^m\ y_i\)。
而此时不符合我们将 \(x_1\) 加入的条件,也就是我们不会加入 \(x_1\),所以不会存在这种情况。
那么性质一也很好说明,每一次没被加入的数都可以表示出来,加入的数在加入后也可以表示出来。
然后就是性质三。其实发现线性基的大小跟插入顺序无关。如果一个元素在一种顺序下不能插入,但是在另一种顺序下插入了,那么一定存在另一个元素原本能插入,但是在后者的顺序下插入不了。
因为在保证性质一后得到的线性基,从里面任意删除一个元素都无法表示 \(A\) 的所有元素,所以得到的线性基的大小一定是最小的。
然后是性质四。因为我们 \(B\) 里的数都可以通过 \(A\) 表示出来,所以 \(B\) 的任意子集也可以表示。
2. 线性基的构建
有两种方式构建线性基。
一种是贪心构造,一种是高斯消元构造,但是前者支持在线。后者的优势在于高斯消元后的矩阵是一个行简化阶梯形矩阵。
这里只给出贪心法的构造,因为贪心法更常用且更好写,最主要是笔者不会行列式,所以后面的性质对我来说没什么用。
贪心法
这里贪心法构造出的方案满足 \(B\) 中元素的二进制最高位不同。
设 \(p_i\) 为线性基 \(B\) 中二进制最高位为 \(i\) 的线性基大小。当 \(p_i=0\) 时,说明这样的数不存在于线性基内。
现在考虑加入一个数 \(w\)。
从大到小枚举每一位,若 \(w\) 当前位为 \(1\),则判断 \(p_i\) 是否为 \(0\),如果为 \(0\) 则让 \(p_i=w\),然后退出。如果 \(p_i\neq 0\),则让 \(w\gets w\ \text{xor}\ p_i\),这样能把 \(w\) 的这一位变为 \(0\) 从而降低它的最高位。
显然当 \(w=0\) 时它能被线性基内的数所表示。
单次插入为 \(O(\log V)\),\(V\) 为值域大小。
注意:当 \(p_i\neq 0\) 时,\(p_i\) 的第 \([0,i-1]\) 位也可能为 \(1\)。也就是可能 \(p_i\neq 2^i\)。
void insert(int w)
{
for(int i=r;i>=0;i--) //r为所有数的二进制最高位的最大值
{
if(w&(1<<i))
{
if(!p[i]) return p[i]=w,void();
w^=p[i];
}
if(!w) return;
}
}
3. 线性基的操作及技巧
求异或最大值
求 \(x\) 与 \(A\) 的子集的最大异或和。
因为构建的线性基的满足二进制最高位不同,所以我们从大到小枚举最高位,然后依次判断取了过后是否更优。
时间复杂度 \(O(\log V)\)。
int ans=x;//x为初始值。也就是找x与A的子集的异或和最大值
for(int i=r;i>=0;i--) //r为值域的二进制的最高位
{
if((ans^p[i])>ans) ans^=p[i];
}
求异或最小值
如果是求 \(x\) 与 \(A\) 的子集的异或和最小值,可以类似于上述求最大值的过程,从高位向低位枚举,如果取了更小就取。
如果是求 \(A\) 的非空子集的异或和最小值,那么检查 \(A\) 中是否有元素未被插入线性基,如果有则答案为 \(0\),否则答案为最小的非零 \(p_i\)。
小Trick:当 \(|A|>r\) 时,其中 \(r\) 为值域的二进制最高位,则一定有元素没被插入线性基。因为线性基最多只能插入 \(r\) 个元素。
时间复杂度 \(O(\log V)\)。
求异或k小值
因为求出的线性基 \(p_i\) 可能会存在除了第 \(i\) 位为 \(1\) 以外其他位也有 \(1\),就可能导致用一个大数异或一个小数可能比不异或小数更小。所以我们需要对线性基先处理一下。
对于 \(p_i\) ,枚举 \(j\ \text{from}\ (i-1)\ \text{to} \ 1\),然后如果 \(p_i\) 的第 \(j\) 位为 \(1\) 的话则让 \(p_i\gets p_i\ \text{xor}\ p_j\)。注意这里的 \(i\) 需要从小到大枚举。
那么此时用一个大数异或上小数就一定大于一个大数了。
将 \(k\) 转化为二进制数,如果 \(k\) 的第 \(d\) 位为 \(1\),则答案异或上 线性基中第 \((d+1)\) 小的元素。
别忘了先把 \(0\) 给判掉。如果最小值为 \(0\),则需要让 \(k\gets k-1\)。
预处理时间复杂度 \(O(\log^2 V)\),单次查询 \(O(\log V)\)。
void operate()
{
for(int i=0;i<=r;i++)
{
for(int j=i-1;j>=0;j--) if(p[i]&(1<<j)) p[i]^=p[j];
}
}
int get_kth(int k)
{
if(k==1&&num<n) return 0;//提前判掉最小值为0的情况
//num为线性基内元素个数
if(num<n) k--;
int res=0;
for(int i=0;i<=r;i++)
{
if(p[i])
{
if(k&1) res^=p[i];
k>>=1;
}
}
return res;
}
int main()
{
operate();//先预处理
return 0;
}
询问存在性
判断一下能否插入线性基即可。时间复杂度 \(O(\log V)\)
线性基求并
对线性基 \(B_1\) 和 \(B_2\) 求并,直接将 \(B_2\) 中的每一个元素加入 \(B_1\) 即可。
时间复杂度 \(O(\log^2 V)\)。
带删除线性基
这里只给出离线做法。
那么求出每个元素被删除的时间戳 \(d_i\),如果没被删就是正无穷。
对于线性基,同时维护一个 \(d'_i\) 表示 \(p_i\) 的时间戳。
然后在加入 \(w\) 时, \(w\) 的第 \(i\) 位为 \(1\),进行分类讨论:
- 当 \(p_i=0\) 时,则让 \(p_i=w,d'_i=d_w\)。
- 当 \(p_i\neq 0\) 时,如果 \(d'_i<d_w\) 时,则 \(\text{swap}(p_i,w),\text{swap}(d'_i,d_w)\)。
这样再进行操作时,只需判断 \(d'_i\) 是否 \(\ge\) 当前时间戳即可。如果 \(\ge\) 当前时间戳,说明 \(p_i\) 还存在,否则就当 \(p_i=0\)。
void insert(int w,int dd)
{
for(int i=r;i>=0;i--)
{
if(w&f[i])
{
if(!p[i]) return p[i]=w,d[i]=dd,void();
if(dd>d[i]) swap(p[i],w),swap(d[i],dd);
w^=p[i];
}
if(!w) return;
}
}
4. 基础题
P3812【模板】线性基
给定 \(n\) 个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
$ 1 \leq n \leq 50, 0 \leq S_i < 2 ^ {50} $
直接全部插入线性基,再求最大值即可。时间复杂度 \(O(n\log V)\)。
不明白数据范围为啥开这么小。
P4570 [BJWC2011] 元素
求出一个长为 \(n\) 的序列 \(a\) 的异或线性基,对于每一个数,把他插入线性基的代价为 \(d_i\)。求线性基的最大代价总和。
\(n\le 10^3,d_i\le 10^4,a_i\le 10^{18}\)。
可能有些同学会有疑问啊?如何求才能使代价最大呢?dp?
其实很简单,因为线性基的大小是定的,所以直接按 \(d_i\) 从大到小的顺序加入 \(a_i\) 即可。如果成功插入,则答案加上 \(d_i\)。可以证明这样贪心一定是对的。
时间复杂度 \(O(n\log V)\)。
P4301 [CQOI2013] 新Nim游戏
一个长为 \(n\) 的正整数序列,你可以删去一个子集。删完后对手也会删去一个没被删过的数的子集。
然后你们在剩余的数中玩nim游戏,你先手。
求出最终你有必胜策略的情况下,你删去子集内元素之和的最小值。
\(n\le 100,a_i\le 10^9\)。
实际上就是想要你删去一个子集后剩下的子集不包含异或和为 \(0\) 的非空子集。
相当于求出将所有数加入线性基后,没被成功插入的数的和的最小值。
直接从大到小排序,然后类似上一道题处理就行了。
5. 进阶题
P4151 [WC2011] 最大XOR和路径
一个无向连通图,一条路径的长度为边权异或和。求出 \(1\) 到 \(n\) 的最长路径。
\(n\le 5\times 10^4,m\le 10^5,w\le 10^{18}\)。
你会发现最终的路径可以表达为 \(1\) 到 \(n\) 的一条链 \(+\) 若干个简单环。而且这条链是任意的,因为我可以通过加入若干与链有公共边的简单环 使得 这条链“变成”了另一条链。
因为一个数异或上自己等于 \(0\)。所以一条路走两遍贡献为 \(0\)。所以一个点出发到达一个简单环,绕一圈后再回来的贡献其实就是简单环上边权的异或和。
而简单环是可以拼接的,因为两个简单环的公共部分的异或和贡献为 \(0\)。
于是我们可以建一棵以 \(1\) 为根的dfs生成树。设 \(dis_u\) 为 \(1\) 到 \(u\) 的树上路径异或和。于是对于所有非树边,与树边构成的简单环为 \(dis_u\ \text{xor}\ dis_v\ \text{xor}\ w\)。
于是将所有只有一条非树边的简单环加入线性基。因为简单环可以合并,所以相当于我们把所有简单环都加入了。
于是最终答案就是 \(dis_n\) 与这个线性基的最大值。
时间复杂度 \(O(n\log V)\)。
P3292 [SCOI2016] 幸运数字
一棵 \(n\) 个节点的树,边带权。 \(q\) 次询问,查询 \(x\) 到 \(y\) 的路径中选出若干条边,求出所有情况下,能选出的这些边的最大异或和是多少
\(n\le 2\times 10^4,q\le 2\times 10^5,w\le 2^{60}\)。
不难想到,我们需要维护出 \(x\) 到 \(y\) 路径的线性基。于是可以考虑用倍增+线性基合并去做。
这样时间复杂度是 \(O(n\log n\log V+q\log n\log ^2V)\)。
然后在 6.0s 的实现下居然过了。
但是很慢,考虑更快的做法。
我们可以对于 \(u\) ,维护出 \(1\) 到 \(u\) 的路径的线性基 \(b_u\)。但是这里是类似“带删除线性基”的,对于 \(p_i\neq 0\) 且 \(w\) 这一位为 \(1\) 的情况下,我们优先选深度较大的。当 \(dep_w>dep'_i\) 时就选 \(w\),否则选 \(p_i\)。
于是对于查询 \((x,y)\),我们先合并 \(b_u\) 和 \(b_v\),同样优先取深度最优的。然后对这个线性基求最大值时,当 \(dep'i<dep_{\operatorname{lca(u,v)}}\) 时,我们把 \(p_i\) 当作 \(0\)。其余不变。
于是这样就能在 \(O(n\log n+q\log^2V)\) 解决问题。
6. 结语
推荐的是 这个题单,还有 P3733 [HAOI2017] 八纵八横。
参考文献:

浙公网安备 33010602011771号