学习笔记——线性基
前言
学校模拟赛的时候差点板题切不掉,痛改前非,决定来好好学一下线性基。
关于真正的线性基
你当我是数竞的吗?还线性基,就我这个勉强搞懂并且不能完全理解线性代数的垃圾怎么可能会线性基啊啊啊你看像这种大佬才有可能会线性基把又不是人人都像你一样天天AKIOI闲着没事捧着本算导搁着天天AK模拟赛没事情干然后把证数学引理当成颓废的人……
哦上面的大概是个病句,但我真不是很想看这块东西所以。
OI 中的线性基
只考虑异或。
没有非常专业的严格证明,但是总是可以感性理解。
线性基的性质
(from oiwiki)
- 线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
- 线性基是满足性质 \(1\) 的最小的集合。
- 线性基没有异或和为 \(0\) 的子集。
- 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。
- 线性基中每个元素的二进制最高位互不相同。
这个大家都知道吧。
维护线性基
先来康康市面上常见的方法。
其核心是线性基中每个元素的二进制最高位是不同的。这样的话,线性基的大小是不超过 \(\log\) 的。但这样为什么是对的呢?我们考虑两个数 \(a\) 和 \(b\),它们的最高位是相同的。那么我们必然可以留下其中一个,并把另一个变成 \(a\oplus b\)。这样的话,如果我留下 \(a\),并且前面需要 \(b\) 来得到异或和,我们掏出一个 \(a\oplus(a\oplus b)=b\),发现其实是等价的。所以对于一个元素,如果有线性基中有元素与它最高位相同了,那么它可以异或上这个元素,然后继续做。
那维护的方法就很明显了。首先我们掏出一个数,然后二进制位从高到低扫过去,如果对于某一位,线性基中没有一个元素最高位是当前位,那么这个数理所当然插入线性基。否则,我们按照上面所说的,给它异或一下,然后继续下一位。如果遍历完了,说明这个数已经可以用线性基中的元素表出了,直接扔掉。
ll p[65];
void insert(ll x){
for(int i=62;i>=0;i--){
if(!(x>>(ll)i)) continue;//如果当前位是 0,那么是无法产生任何贡献的,直接跳过就可以了
if(!p[i]){p[i]=x;break;}//如果当前位为最高位的没有,那么插入,退出
x^=p[i];//否则按上面说的异或后继续
}
}
然后是查询某个数是否能被线性基中的元素异或得到。这样我们根据上面的说法,把它当成插入元素,如果扫到底了,那就是能异或得到,否则不能。
ll p[65];
bool query(ll x){
for(int i=62;i>=0;i--){
if(!(x>>(ll)i)) continue;
if(!p[i]) return 0;
x^=p[i];
}return 1;
}
然后是查询能异或得到的最大值,我们考虑每个二进制位。如果能够使得一个高位的二进制位是 \(1\),那么后面长什么样子都在所不惜。所以我们从高到低来扫这个线性基,如果异或上当前值能够使答案变大,那么就异或。
ll qmax(){
ll ans=0;
for(int i=62;i>=0;i--)
if((ans^p[i])>ans)
ans^=p[i];
return ans;
}
晚上回去问了 \(\texttt{c}\color{Red}{\texttt{mll02}}\),说是用高消求线性基反正没啥用,所以就不看了。