算法学习笔记(12): 线性基
线性基
熟练掌握异或运算是食用本文的大前提,请读者留意
是什么?
是一种利用线性代数的知识,用于解决异或问题的一种手段(不能算作数据结构吧这)
本文并不会涉及到线性代数。而是从OI基础算法思想的角度阐释线性基。尽管这可能违背了设计该方法的初衷。
一般来说,预处理的时间复杂度为 其中 一般为 ,也就是 。单次操作一般也是 的复杂度。
可以做什么?
-
查询异或和第 小
-
查询异或和最大值(最常用)
-
某个数可否被异或出来
-
……
三点性质
-
线性基内任何几个非0的数异或起来都不可能为0
-
原序列里面的任意一个数都可以由线性基内的几个数异或得来
-
线性基内数个个数在序列一定的情况下一定,但是线性基内的值不一定
-
数的个数在保证性质2的情况下是最少的,且不超过位长
如何实现?
xxj
是一个数组,至于为什么用这个名字……
令 xxj[i]
表示最高为 1 的位 i
的一个异或和(如果有的话)。
我们只需要维护这么 个数就行了
我们可以这么理解,
0100
可以由0101
和0001
异或和起来,那么我们只需要维护0101
和0001
即可。
为什么可以保证性质2?
可以理解为贪心,一位一位的拼凑出任意一个数。
严谨的证明就需要用到更深层的知识了,这里不做说明
新增一个数
考虑如何新增一个数 x
进去?
由于我们维护的是异或和,那么对于 xxj
内的任何元素都可以异或上 x
以保证至多只有 个数。
假设 x
最高为 1 的位为 i
,如果 xxj[i]
已经存在了一个数,那么我们将 x
异或上 xxj[i]
,重复上述步骤,否则,就令 xxj[i] = x
。
如果,最终 x
变成了 0,那么证明,用之前存在的数可以凑出 x
,否则,不可以。
其实,这既是维护基的方法,也是判断某个数是否可以被异或出来的方法……
查询可以异或出来的最大值
这里,我们利用贪心的思想,先找到xxj
内最大的数,也就是最高为 1 的为最高的那个数。
很明显,如果
i < j
那么xxj[i] < xxj[j]
。
假如我们找到了 xxj[i]
,令 res = xxj[i]
,然后逆序 j
枚举 [0, i)
,如果,res
的第 j
位不为 1,那么res ^= xxj[j]
。
但是……我们的实现肯定不能如此复杂,那么就考虑贪心,如果 j
位为 变为了 1,那么之后的所有位都变为 1 做出的贡献都没有这个位做出的贡献大,那么我们优先保证最高位为1即可。
int qmax() {
int ans = 0;
for (int i = 30; i >= 0; --i) {
ans = max(ans, ans ^ xxj[i]);
}
return ans;
}
解释了跟没解释一样……
查询异或和第 小
我们先解决 k = 1
的特殊情况。
显而易见,就是最小的 xxj[i]
了。
但是,如果数列的数的个数比线性基内的数的个数多,那么很明显,最小值为 0。
那么特殊化一下。
其实就完全变化了
我们先将整个 xxj
数组处理一下。由大到小枚举 xxj[i]
。对于个 xxj[i]
,我们尽量保证其二进制下1的个数最少。那么,可以保证线性基内的第 k
个数是第 2^(k-1)
大的。
二进制拆分一下,便是答案
#define marked(x, i) (((x)>>i)&1)
int prepare() {
for (int i = 30; i >= 0; --i) {
for (int j = i - 1; j >= 0; --j) {
if (marked(xxj[i], j)) xxj[i] ^= xxj[j];
}
int kth(int k) {
prepare();
int res = 0;
for (int i = 0; i < 30; ++i) {
if (xxj[i]) {
if (k & 1) res ^= xxj[i];
k >>= 1;
}
}
return res;
}
例题
模板题我们就不放了吧……
这是我们考试的一道题……至于为什么会与线性基扯上关系……我在题解中是这么写的
所以,加油吧,勤奋学习的人们!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?