线性基学习笔记
线性基主要用来解决异或问题
线性基的性质
-
原序列的任何一个数都可以由线性基中的若干个元素异或得到
-
线性基中的任何元素互相异或,不可异或出0
我们考虑到异或的性质:
- 交换律:如果 a 1 ⊕ a 2 ⊕ a 3 = a 4 a_1 \oplus a_2 \oplus a_3 = a_4 a1⊕a2⊕a3=a4 那么 a 2 ⊕ a 1 ⊕ a 3 = a 4 a_2 \oplus a_1 \oplus a_3 = a_4 a2⊕a1⊕a3=a4 很容易证明
- a 1 ⊕ a 2 = a 3 a_1 \oplus a_2 = a_3 a1⊕a2=a3 则 a 1 ⊕ a 3 = a 2 a_1 \oplus a_3 = a_2 a1⊕a3=a2 两边同时异或 a 2 a_2 a2 即可证明
线性基的构造
如何构造线性基呢,我们定义一个数组p[i]表示最高位在第i位上的数是多少,那这样构造为什么是对的呢,看一组例子: 5 1 6 5 \ 1 \ 6 5 1 6 这一组数
它的线性基是多少呢,逐渐将每一个数插入线性基
p [ 2 ] = 5 p[2]=5 p[2]=5, p [ 0 ] = 1 p[0]=1 p[0]=1,当插入到 6 6 6时,我们发现 p [ 2 ] p[2] p[2]已经有值了 ,那如何插入这个 6 6 6来保证线性基的正确性
考虑到异或的性质2: a 1 ⊕ a 2 = a 3 a_1 \oplus a_2 = a_3 a1⊕a2=a3 则 a 1 ⊕ a 3 = a 2 a_1 \oplus a_3 = a_2 a1⊕a3=a2
如何保证在不损失 6 6 6的情况下插入 6 6 6,不妨让 6 ⊕ p [ 2 ] 6 \oplus p[2] 6⊕p[2],消除掉 6 6 6的最高位,然后继续插入。考虑到异或的交换律,设 p [ 2 ] ⊕ 6 = x p[2] \oplus 6=x p[2]⊕6=x,那么新插入的这个数x, x ⊕ p [ 2 ] = 6 x \oplus p[2]=6 x⊕p[2]=6,由于线性基的性质1,从而保证了线性基插入的正确性。
伪代码:
void insert(ll *p,ll x)
{
for(int i=63;i>=0;i--)
{
if((x>>i)&1)
{
if(!p[i]){p[i]=x;break;}
else x^=p[i];
}
}
}
//p是线性基数组,x是插入的值
线性基的应用
一、判断一个数是否可以被一个序列异或出来
考虑到线性基的构造:
一个数被插入到线性基时,分为两种情况:
-
可以被插入线性基:
假设插入数 x x x ,如果被插入到第i位,那么 p [ i ] = x ⊕ p [ a ] ⊕ p [ b ] ⊕ … … ⊕ p [ z ] p[i]=x \oplus p[a] \oplus p[b] \oplus……\oplus p[z] p[i]=x⊕p[a]⊕p[b]⊕……⊕p[z], a a a, b b b, c c c……代表 i i i 的更高位上 p p p 数组已经有值的位置
-
不能被插入线性基:
为什么不能被插入,说明在 x x x 插入的时候,不断异或某些已经 p p p 数组有值的位置的过程中,变成了 0 0 0,即 x ⊕ p [ a ] ⊕ p [ b ] ⊕ ⋯ ⊕ p [ z ] = 0 x \oplus p[a] \oplus p[b] \oplus \cdots \oplus p[z]=0 x⊕p[a]⊕p[b]⊕⋯⊕p[z]=0,此时无法被插入到线性基。
一个数不能被插入线性基说明这个元素一定可以被这个线性基异或出来,也就是一定可以被这个序列某些元素异或出来,为什么呢,在插入的过程中 x ⊕ p [ a ] ⊕ p [ b ] ⊕ ⋯ ⊕ p [ z ] = 0 x \oplus p[a] \oplus p[b] \oplus \cdots \oplus p[z]=0 x⊕p[a]⊕p[b]⊕⋯⊕p[z]=0,也就是 p [ a ] ⊕ p [ b ] ⊕ ⋯ ⊕ p [ z ] = x p[a] \oplus p[b] \oplus \cdots \oplus p[z]=x p[a]⊕p[b]⊕⋯⊕p[z]=x。所以判断一个数是否可以被这个序列异或,只需要判断这个数是否可以插入到这个线性基里面。
为了方便起见,可以直接改写构造代码如下:
bool insert(ll *p,ll x)
{
for(int i=63;i>=0;i--)
{
if((x>>i)&1)
{
if(!p[i]){p[i]=x;break;}
else x^=p[i];
}
if(x==0)return false;
}
return true;
}
二、求一个序列的若干个元素异或的最大值
考虑到线性基数组是从高位到低位的,所以我们贪心从高位向低位异或即可的出答案
贪心的正确性:
假如现在的ans从高位向低位异或,异或到了第i位,分为两种情况
-
如果 a n s ans ans的二进制第i位不为1:
那么 a n s ⊕ p [ i ] > a n s ans \oplus p[i] > ans ans⊕p[i]>ans,显而易见 a n s ans ans异或完之后第 i i i 位为 1 1 1,一定变得更大。
-
如果 a n s ans ans的二进制第i位为1:
那么 a n s ⊕ p [ i ] < a n s ans \oplus p[i] < ans ans⊕p[i]<ans,显而易见 a n s ans ans异或完之后第 i i i 位为 0 0 0,一定变得更小。
所以如果 a n s ⊕ p [ i ] > a n s ans \oplus p[i] > ans ans⊕p[i]>ans就让 a n s ans ans异或 p [ i ] p[i] p[i]
伪代码:
ll maxans(ll *p)
{
ll ans=0;
for(int i=63;i>=0;i--)//记得从线性基的最高位开始
if((ans^p[i])>ans)ans^=p[i];
return ans;
}
三、求一个序列的若干个元素异或的最小值
显而易见答案为 0 0 0 或 p [ 0 ] p[0] p[0]。
因为线性基异或不出 0 0 0,所以如果原序列可以异或出 0 0 0,那 a n s ans ans 就是 0 0 0,否则就是 p [ 0 ] p[0] p[0],因为 p [ 0 ] p[0] p[0]异或任何线性基中的元素都会变大
四、求一个序列的若干个元素异或的第k小值
二进制原理 ,先预先处理线性基数组,保证 p [ i ] p[i] p[i] 已经是这一位在线性基里面最小的,只要让 p [ i ] p[i] p[i] 的每一位异或上对应的位置,即可使其变得更小。
即,对于每一个 p [ i ] p[i] p[i] ,枚举 j = i − 1 j=i-1 j=i−1 到 j = 0 j=0 j=0 ,如果 p [ i ] p[i] p[i] 的第 j j j 位为 1 1 1,那么 p [ i ] p[i] p[i] 异或上 p [ j ] p[j] p[j]。
然后将 k k k 转化成 2 2 2 进制,再用 2 2 2 进制的 1 1 1 、 0 0 0 的位置异或即可。
详见代码:
void work(ll *p)
{
for(int i=63;i>=0;i--)
{
for(int j=i-1;j>=0;j--)
{
if((p[i]>>j)&1)p[i]^=p[j];
}
}
}//预处理
ll maxk(ll *p,ll k)
{
if(k==1&&flag)return 0;
//flag表示原序列可以异或0
if(flag)k--;
work();
ll ans=0;
for(int i=0;i<=63;i++)
{
if(p[i])
{
if(k&1)ans^=p[i];
k>>=1;
}
}
return ans;
}