线性基详解

线性基详解

线性基主要用来解决异或问题

线性基的性质

  1. 原序列的任何一个数都可以由线性基中的若干个元素异或得到

  2. 线性基中的任何元素互相异或,不可异或出0

我们考虑到异或的性质:

  1. 交换律:如果a1 ^ a2 ^ a3 = a4 那么a2 ^ a1 ^ a3 = a4 很容易证明
  2. a1 ^ a2 = a3 则a1 ^ a3 = a2 两边同时异或a2 即可证明

线性基的构造

如何构造线性基呢,我们定义一个数组p[i]表示最高位在第i位上的数是多少,那这样构造为什么是对的呢,看一组例子:5 1 6 这一组数

它的线性基是多少呢,逐渐将每一个数插入线性基

p[2]=5,p[0]=1,当插入到6时,我们发现p[2]已经有值了 ,那如何插入这个6来保证线性基的正确性

考虑到异或的性质2:a1 ^ a2 = a3 则a1 ^ a3 = a2

如何保证在不损失6的情况下插入6,不妨让6^p[2],消除掉6的最高位,然后继续插入。考虑到异或的交换律,设p[2] ^ 6=x,那么新插入的这个数x,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是插入的值

线性基的应用

一、判断一个数是否可以被一个序列异或出来

考虑到线性基的构造:

一个数被插入到线性基时,分为两种情况:

  1. 可以被插入线性基:

    假设插入数x,如果被插入到第i位,那么p[i]=x ^ p[a] ^ p[b] ^……p[z], a,b,c……代表i的更高位上p数组已经有值的位置

  2. 不能被插入线性基:

    为什么不能被插入,说明在x插入的时候,不断异或某些已经p数组有值的位置的过程中,变成了0,即x ^ p[a] ^ p[b] ^……p[z]=0,此时无法被插入到线性基。

一个数不能被插入线性基说明这个元素一定可以被这个线性基异或出来,也就是一定可以被这个序列某些元素异或出来,为什么呢,在插入的过程中x ^ p[a] ^ p[b] ^……p[z]=0,也就是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位,分为两种情况

  1. 如果ans的二进制第i位不为1:

    那么ans ^ p[i] > ans,显而易见ans异或完之后第i位为1,一定变得更大。

  2. 如果ans的二进制第i位为1:

    那么ans ^ p[i] < ans,显而易见ans异或完之后第i位为0,一定变得更小。

所以如果ans ^ p[i] > ans就让ans异或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或p[0]。

因为线性基异或不出0,所以如果原序列可以异或出0,那ans就是0,否则就是p[0],因为p[0]异或任何线性基中的元素都会变大

四、求一个序列的若干个元素异或的第k小值

二进制原理 ,先预先处理线性基数组,保证p[i]已经是这一位在线性基里面最小的,只要让p[i]的每一位异或上对应的位置,即可使其变得更小。

即,对于每一个p[i],枚举j=i-1到j=0,如果p[i]的第j位为1,那么p[i]异或上p[j]。

然后将k转化成2进制,再用2进制的1、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;
}

posted @ 2023-07-17 18:45  Crymx  阅读(72)  评论(0)    收藏  举报