线性基
概念
对于任意一个整数序列,都有线性基,且对同一序列,其线性基的个数是唯一的,但线性基里的数未必相同。
性质
线性基有三个基本性质
- 原序列中的任意一个元素都可以通过线性基内的某些元素异或得到
- 线性基内的任意一些元素相异或都不可能得到0
- 对于一个序列,其线性基的元素个数是确定的,且是所有满足性质1的数列中元素个数最少的。
求线性基
求一个序列的线性基说白了就是一个把序列中的数不断插入线性基的过程。
对于一个有n个元素的序列a,我们用一个d数组来存储它的线性基。
void ins (ll a[],int n)
{
for(int i=0;i<n;++i)
{
for(int j=65;j>=0;j--)
{
if(a[i]&(1ll<<j))//这里ll代表long long ,如果序列数过大,j>=31那么一定要加ll
{
if(d[j]==0)
{
d[j]=a[i];
break; //注意这里一定要break;
}
else a[i]^=d[j];
}
}
}
}
仔细体会上面的插入过程,不难发现线性基的每一个元素 d[i] 其二进制表示最高位为 i+1。
每次插入时, j 从 65 (用 65 是为了足以应付 64 位的long long 类型)开始向下遍历,直到遍历到插入数的第 j+1 位为 1 时,此时 j+1 一定是x的最高位;如果此时 d[i] 为 0 ,结果显然成立;如果 d[i] 非0 x异或上d[j],此时x的j+1位变为0,新的 x 的最高位就不是 j+1 了,然后 j 接着向下寻找新 x 的最高位。
线性基性质证明
这里首先简单说一下异或的简单性质,不知道原理的可以手动模拟一下
所谓异或,就是当两个数的二进制形式同一位上的数相同时(同为0或1),该位上的异或结果就为0,否则为1。因此不难得出:
- 0 ^ a = a
- a ^ a = 0
- a ^ b = c <=> a ^ c = b
因此假设插入数为x,当它被插入d数组中的 dx 中之前势必与 0 个或多个d数组中的元素 da,db…进行异或,则有:
da ^ db ^ …^ x = dx
根据异或的第一条性质有:
线性基性质一得证。
对性质二用反证法假设 da ^ db ^ dc = 0 成立且da,db比dc更早插入线性基,则:
由上述插入过程,dc不应插入线性基,与实际情况矛盾
线性基性质二得证。
对性质三,同样设 :
线性基性质三得证。
线性基的运用
一、 从一个数列中取若干数,使他们异或所得到的值最大
从线性基的最高位开始往下遍历,如果当且答案异或上线性基的当前元素能使答案变大,那就异或上它,反之不用管它,继续向下遍历到底。
ll gtans()
{
ll ans=0;
for(int i=65;i>=0;i--)
if(ans<(ans^d[i]))ans^=d[i];
return ans;
}
为什么是一个对线性基的简单贪心过程呢?因为线性基其实是将原序列的n个数映射到线性基的六十几个数中,线性基中的元素要么等于原序列中的元素,要么是原序列中的几个元素相异或的结果,因此求得的ans虽是由线性基中的元素异或而成的,但它一定可由原序列中得几个元素相异或得到。
二、 从一个数列中取若干数,使他们异或所得到的值最小
由上面我们知道线性基中的元素要么等于原序列中的元素,要么是原序列中的几个元素相异或的结果。因此很显然最小值就是线性基中最小得元素,求最小元素得值大家都会,不再赘述。
三、求第k小的值
我们将原序列的数插入的到的线性基满足最高位的唯一性,对于d[i]来说,它的最高位是i+1位,但是可能存在d[j] ( j>i),它的第i+1位也是1,这里我们先对线性基进行处理,如果d[j]的二进制的第i位为1我们就给他异或上d[i-1],这样处理完线性基后能保证仅有d[i]的第i+1位为1,这样处理完后的d数组(它仍然是一个线性基,易证它符合线性基的三个性质)中任意元素相异或的结果仍然能够由原序列的数异或得到,且d数组中任意元素相异或总是会变大。
处理完毕后我们就能根据k的值求的第k小的值,我们可以将从小到达排列的新线性基中的元素对应到k二进制形式的每一位上,例如k=2,那么答案就是新线性基倒数第二小的元素,如果k=3,那么就是倒数第二和倒数第一的元素的异或结果…
ll gtans(ll k)
{
if(k==1&&cnt<n)return 0;//cnt是线性基中元素个数,cnt<n说明原序列中有元素不能成功插入线性基,说明原序列元素异或能得到0
if(cnt<n)k--;//线性基中异或是得不到0的所以k要减去1
for(int i=1;i<=65;++i)
{
for(int j=0;j<i;++j)
if(d[i]&(1ll<<j))d[i]^=d[j];
}
ll ans=0;
for(int i=0;i<=65;i++)
if(d[i]!=0)
{
if(k%2==1)ans^=d[i];
k/=2;
}
}