废话

其实如果有一些线代基础的话这个东西很好理解,这篇博客是写给不了解,线性空间等概念的同学们的,如果觉得自己基础好可以换一篇看,网上有很多需要一些基础才能看的博客。

update on 2020.4.6: 大巨巨在评论中指出了以前写的的删除操作有 b u g bug bug,仔细思考后稍微修改了一下(然而跟我在评论中写的做法并不一样qwq),并且更新了整篇文章的排版。


线性基是啥?

线性基是一个数的集合,并且每个序列都拥有至少一个线性基,取线性基中若干个数异或起来可以得到原序列中的任何一个数。

线性基三大性质

  1. 原序列里面的任意一个数都可以由线性基里面的一些数异或得到
  2. 线性基里面的任意一些数异或起来都不能得到 0 0 0
  3. 线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的

线性基的构造

那么它是怎么构造的呢?

设数组 d d d 表示序列 a a a 的线性基,下标从 0 0 0 开始算。(为了方便理解,我们设 x ( 2 ) x_{(2)} x(2) x x x 的二进制数)

先给代码:

void add(ll x)
{
    for(int i=60;i>=0;i--)
    {
        if(x&(1ll<<i))//注意,如果i大于31,前面的1的后面一定要加ll
        {
            if(d[i])x^=d[i];
            else
            {
                d[i]=x;
                break;//插入成功就退出
            }
        }
    }
}

据此,我们可以得到一个关于d数组的性质:若 d [ i ] d[i] d[i] 不为0,则 d [ i ] d[i] d[i](2) 的第 i + 1 i+1 i+1 位一定为 1 1 1,并且 d [ i ] d[i] d[i](2) 的最高位就是第 i + 1 i+1 i+1 位。

为了更好地进行线性基的讲解,我们要先知道关于异或的一个小性质(巨佬们可以跳过qwq):

如果满足 a a a ^ b b b ^ c = 0 c=0 c=0,那么 a a a ^ b = c b=c b=c,所以如果 a a a ^ b = c b=c b=c,那么 a a a ^ c = b c=b c=b

证明显然,这里就不赘述了。


证明性质1

我们知道了线性基的构造方法后,其实就可以很容易想到如何证明性质 1 1 1 了,我们设原序列里面有一个数 x x x,我们尝试用它来构造线性基,那么会有两种结果——1、不能成功插入线性基;2、成功插入线性基。

分类讨论一下

1、不能成功插入线性基**

什么时候不能插入进去呢?

显然就是它在尝试插入时异或若干个数之后变成了 0 0 0

那么就能得到: x x x ^ d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] d[c] d[c] ^ . . . = 0 ...=0 ...=0

根据上面的那个小性质,则有: d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] d[c] d[c] ^ . . . = x ...=x ...=x

所以,如果 x x x 不能成功插入线性基,一定是因为当前线性基里面的一些数异或起来可以等于 x x x

2、可以成功插入线性基

我们假设 x x x 插入到了线性基的第 i i i 个位置,显然,它在插入前可能异或若干个数,那么就有:

x x x ^ d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] d[c] d[c] ^ …= d [ i ] d[i] d[i]

d [ i ] d[i] d[i] ^ d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] d[c] d[c] ^ …= x x x

所以显然, x x x 此时也可以由线性基里面的若干个数异或得到。

综上,性质1得证。


再看性质2

大佬肯定认为这是一条显然的性质啊,但为了严谨一点,还是给出证明吧:

我们使用反证法:

d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] = 0 d[c]=0 d[c]=0(其中 d [ c ] d[c] d[c] d [ a ] d[a] d[a] d [ b ] d[b] d[b] 要更晚被插入线性基),那么有 d [ a ] d[a] d[a] ^ d [ b ] = d [ c ] d[b]=d[c] d[b]=d[c]

∵ d [ c ] \because d[c] d[c]可以由 d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b]得到

∴ d [ c ] \therefore d[c] d[c]不可能插入线性基

故假设不成立,所以线性基中不存在有任何数异或起来可以得到 0 0 0


最后看性质3

这个性质被BJWC拿来出过一道题,那题网上不知道为什么没有证明(都是草草的给出做法然后贴代码),然而如果你熟记这个性质3,那么很快就能想明白那题。(这题在文章末尾会给出)

那么我来尝试证明性质3 qwq(毕竟网上几乎找不到证明作为参考,这里笔者我只能乱推了):

还是没什么卵用地分类讨论一下

1、假如序列里面的所有元素都可以插入到线性基里面

显然如果是这种情况的话,不管是用什么顺序将序列里的数插入到线性基里,线性基中的元素一定与原序列元素数量相同。所以性质3成立。

2、假如序列里面的一些元素不能插入到线性基里面

我们设x不能插入到线性基里面,那么一定满足形如 d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] = x d[c]=x d[c]=x 的式子,那我们尝试将插入顺序改变,变成: d [ a ] d[a] d[a] d [ b ] d[b] d[b] x x x d [ c ] d[c] d[c]。那么显然,d[c]是不可能插入成功的,简单的证明:

∵ \because d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ d [ c ] = x d[c]=x d[c]=x

∴ \therefore d [ a ] d[a] d[a] ^ d [ b ] d[b] d[b] ^ x = d [ c ] x=d[c] x=d[c](根据上面那条并没有什么卵用的异或性质)

原来是 x x x插入不进去,改变顺序后, d [ c ] d[c] d[c]插入不进去,也就是说,对于插入不进去的元素,改变插入顺序后,要么还是插入不进去,要么就是插入进去了,同时另一个原来插入的进去的元素插入不进去了,所以,可以插入进去的元素数量一定是固定的。

显然,如果你去掉线性基里面的任意一个数,都会使得原序列里的一些(或一个)数无法通过用线性基里的元素异或得到,所以,每一个元素都是必要的,换句话说,这里面没有多余的元素,所以,这个线性基的元素个数在保持性质1的前提下,一定是最少的。

如何求最大值

完整的说,是如何求在一个序列中,取若干个数,使得它们的异或和最大。

首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为 0 0 0

代码如下:

ll ans()
{
    ll anss=0;
    for(int i=60;i>=0;i--)//记得从线性基的最高位开始
    if((anss^d[i])>anss)anss^=d[i];
    return anss;
 }   

为啥求解是个贪心的过程?

前面说过, d [ i ] d[i] d[i](2)的第 i + 1 i+1 i+1位一定为 1 1 1,联想一下这里,无非就是两种情况:

  1. a n s ans ans(2)的第 i + 1 i+1 i+1位为 0 0 0
    对于这种情况, a n s ans ans 异或了 d [ i ] d[i] d[i] 之后一定会变大,那么我们就选择异或它。
    可能有人会问, a n s ans ans 异或完 d [ i ] d[i] d[i] 后虽然i+1位变成了1,但是后面的 1 1 1 ~ i i i 位可能会受到影响啊。
    管它呢~
    无论后面怎么变,就算后面的 1 1 1 ~ i i i 位都能变成1,贡献也没有第 i + 1 i+1 i+1 位变成1的贡献大呀。
  2. ans(2)的第 i + 1 i+1 i+1 位为 1 1 1
    如果 a n s ans ans 异或了 d [ i ] d[i] d[i],那么第 i + 1 i+1 i+1 位就会变成 0 0 0,后面怎么异或也补救不了第 i + 1 i+1 i+1 位变成 0 0 0 的损失了,按照上面的思想,要优先使最高位尽可能大,所以此时, a n s ans ans 不能异或 d [ i ] d[i] d[i]

如何求最小值

注意,这里指的是用线性基内的元素能异或出的最小值。

显然就是最小的 d [ i ] d[i] d[i] 了,因为最小的 d [ i ] d[i] d[i] 无论异或谁都会变大。

如果是求整个序列能异或出的最小值而不是这个序列的线性基能异或出的最小值的话,要另外看一看有没有元素不能插入线性基,如果有,那么最小值就是 0 0 0,否则依然是最小的 d [ i ] d[i] d[i]

代码就不用了吧qwq

如何求第k小的值

完整的说,应该是——从一个序列中取任意个元素进行异或,求能异或出的所有数字中第 k k k小的那个。

首先,要对这个序列的线性基处理一下,对于每一个 d [ i ] d[i] d[i],枚举 j = i j=i j=i   t o to to  1 1 1,如果 d [ i ] d[i] d[i](2)的第j位为1,那么 d [ i ] d[i] d[i] 异或 d [ j − 1 ] d[j-1] d[j1]

那么处理完一个线性基之后,应该大致是长这个样子的(x表示0或1):
       1xxxx0xxx0x
                1xxx0x
                        1x

求解过程:将 k k k先转成二进制,假如 k k k 的第 i i i 位为 1 1 1 a n s ans ans 就异或上线性基中第 i i i 个元素(注意不是直接异或 d [ i − 1 ] d[i-1] d[i1] )。

代码如下:

void work()//处理线性基
{
	for(int i=1;i<=60;i++)
	for(int j=1;j<=i;j++)
	if(d[i]&(1ll<<(j-1)))d[i]^=d[j-1];
}
ll k_th(ll k)
{
	if(k==1&&tot<n)return 0;//特判一下,假如k=1,并且原来的序列可以异或出0,就要返回0,tot表示线性基中的元素个数,n表示序列长度
	if(tot<n)k--;//类似上面,去掉0的情况,因为线性基中只能异或出不为0的解
	work();
	ll ans=0;
	for(int i=0;i<=60;i++)
	if(d[i]!=0)
	{
		if(k%2==1)ans^=d[i];
		k/=2;
	}
}

回想上面的线性基处理过程,可以发现,处理完之后,线性基中的元素,作用其实都是提供自己最高位上的 1 1 1,那么只要使提供出来的 1 1 1 可以和 k k k(2) 的每一位上的 1 1 1 对应,那么求出来的 a n s ans ans 一定就是第 k k k 小的。

补充:仔细想想就能知道,其实处理完之后的线性基其实也还是原序列的一个线性基,因为依然拥有上面的三个性质,要知道,一个序列的线性基不唯一,只是元素数量唯一而已。

如何判断一个数是否能被当前线性基中的元素异或得到

把它尝试插入进线性基里面去,假如可以插入,说明不能异或得到,假如插不进去,则说明可以异或得到 (原理然而上面已经讲了)

线性基删除操作

没想到吧,这东西还支持删除操作。(虽然并没有什么题考这个qwq)

在线

具体的问题是这样的:

给一个序列,有三种操作,一是往序列中插入一个数,二是删除这个序列中的一个数,三要求你维护这个序列的线性基。

插入很好解决,插就完了。

重点是删除操作,如果要删除的数 x x x 在线性基外,那么直接删掉即可,问题是假如它在线性基内,把他删掉之后可能序列中其他的数可以填进来。

现在讨论一下 x x x 在线性基内的做法:

没有在线性基中的数,一定是因为线性基中的若干个数可以异或得到他,那么可以记录一下不在线性基中的数都是由线性基中的哪些数异或得到的,那么每一个线性基外的数对应一个集合 S S S,这个集合内就是线性基中那些异或起来可以得到他的数。

假如线性基外的某一个数的 S S S 中包含 x x x,那么就不需要删除 x x x,把这个数删除即可。

原因是这个数在线性基中是可以代替 x x x的,那么就当这个数代替了 x x x,然后 x x x 被删除了,然后把线性基中的 x x x 当做这个数即可,这样的话线性基不会有变化。(实现起来并不需要维护集合 S S S,而是直接维护有哪些数可以代替线性基中的数就好了)

假如 x x x 不被线性基外的任何一个数的 S S S 包含,那么就另外造一个集合 P P P,记录线性基中这个数插入进来的时候异或过哪些数。然后找到线性基中最小的并且 P P P 包含 x x x 的数,让他异或线性基中其他包含 x x x 的数即可(包括自己,自己异或完自己后自己这一位就变成了 0 0 0),这样就能消除 x x x 在线性基中的影响(事实上就等价于用最小的数代替了它)。

总之,由于如果修改了线性基中的某一位会影响到一些比它小的位,所以一般不能修改,要么改最小的并且不会影响到下面的位。

离线

上面的问题并没有强制在线,所以也可以离线做。

离线的话其实更简单。

我们可以找到线性基中对于每一个数,可以代替他们的那些数,那么可以使线性基优先存删除时间晚的,那么就消除了上面的把他删掉之后可能序列中其他的数可以填进来这样的问题,其余操作一样。

具体实现起来就是在插入一个新数的时候,对比一下这个数的删除时间当前枚举到的线性基的某一位的删除时间,假如比他晚就直接替换掉,否则异或它然后继续枚举。这样就少维护了一个集合。


模板题

更多好题:

(难度非递增)

BJWC 2011 元素 | 题解
SCOI 2016 幸运数字 | 题解
TJOI 2008 彩灯 | 题解

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐