线性基 学习笔记

实际上就是通过一个可重集 \(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] 八纵八横

参考文献:

洛谷日记-【学习笔记】浅谈异或线性基

oi wiki - 线性基

posted @ 2025-03-27 21:24  Twilight_star  阅读(19)  评论(0)    收藏  举报