学习来自:https://www.cnblogs.com/ljh2000-jump/p/5869991.html
和 https://blog.csdn.net/a_forever_dream/article/details/83654397
此处只拿出他们做题总结的部分,原理过程删掉了
1、线性基:
若干数的线性基是一组数a1,a2,...an,其中ax的最高位的1在第x位。
通过线性基中元素xor出的数的值域与原来的数xor出数的值域相同。
2、线性基的构造法:
对每一个数pp从高位到低位扫,扫到第x位为1时,若ax不存在,则ax=pax=p并结束此数的扫描,否则令p=p xor ax。
3、查询:
用线性基求这组数xor出的最大值:从高往低扫ax,若异或上ax使答案变大,则异或。
4、判断:
用线性基求一个数能否被xor出:从高到低,对该数每个是1的位置x,将这个数异或上axax(注意异或后这个数为1的位置和原数就不一样了),若最终变为0,则可被异或出。当然需要特判00(在构造过程中看是否有p变为0即可)。例子:(11111,10001)(11111,10001)的线性基是a5=11111,a4=01110,要判断11111能否被xor出,11111 xora5=0,则这个数后来就没有是1的位置了,最终得到结果为00,说明11111能被xor出。
关于线性基的模板:
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define mem(a,x) memset(a,x,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a[N],p[100];
int n;
int insert(ll x){
for(int j=62;j>=0;j--) {
if(!(x>>j&1)) continue;
if(!p[j]) { p[j]=x; return 1; }
x^=p[j];
}
return 0;
}
一些性质:
你可以理解为将一个序列处理完之后得到的产物,并且有如下性质(后面有证明):
1、原序列里面的任意一个数都可以由线性基里面的一些数异或得到。
2、线性基里面的任意一些数异或起来都不能得到0 00
3、线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
证明性质1
我们知道了线性基的构造方法后,其实就可以很容易想到如何证明性质1了,我们设原序列里面有一个数x xx,我们尝试用它来构造线性基,那么会有两种结果——1、不能成功插入线性基;2、成功插入线性基。
分类讨论一下
1、不能成功插入线性基**
什么时候不能插入进去呢?
显然就是它在尝试插入时异或若干个数之后变成了0 00。
那么就有如下式子:
x ^ d[a]^ d[b] ^ d[c] ^...=0
根据上面的那个小性质,则有:
d[a] ^ d[b] ^ d[c] ^...=x
所以,如果x不能成功插入线性基,一定是因为当前线性基里面的一些数异或起来可以等于x。
2、可以成功插入线性基
我们假设x插入到了线性基的第i个位置,显然,它在插入前可能异或若干个数,那么就有:
x ^ d[a] ^ d[b] ^ d[c]^ …=d[i]
d[i]^ d[a] ^ d[b] ^ d[c] ^ …=x
所以显然,x此时也可以由线性基里面的若干个数异或得到。
综上,性质1得证
再看性质2
各位大佬肯定认为这是一条显然的性质啊,但为了严谨一点,还是给出证明吧:
我们使用反证法
设d[a] ^ d[b]^ d[c]=0(其中d[c]比d[a]和d[b]要更晚被插入线性基)
那么有d[a] ^ d[b]=d[c]
∵d[c] 可以由d[a]^ d[b]得到
∴d[c]不可能插入线性基
故假设不成立,所以线性基中不存在有任何数异或起来可以得到0。
最后看性质3
这个性质被BJWC拿来出过一道题,那题网上几乎找不到证明(都是草草的给出做法然后贴代码),然而如果你熟记这个性质3,那么很快就能想明白那题。(这题在文章末尾会给出)
那么我来尝试证明性质3(毕竟网上几乎找不到证明作为参考,这里笔者我只能乱推了):
还是没什么卵用地分类讨论一下
1、假如序列里面的所有元素都可以插入到线性基里面
显然如果是这种情况的话,不管是用什么顺序将序列里的数插入到线性基里,线性基中的元素一定与原序列元素数量相同。所以性质3成立。
2、假如序列里面的一些元素不能插入到线性基里面
我们设x不能插入到线性基里面,那么一定满足形如d[a] d[a]d[a] ^ d[b] d[b]d[b] ^ d[c]=x d[c]=xd[c]=x的式子,那我们尝试将插入顺序改变,变成:d[a]、d[b]、x、d[c]。那么显然,d[c]是不可能插入成功的,简单的证明:
d[a]^ d[b] ^ d[c]=x
d[a]^ d[b]^ x=d[c] (根据上面那条并没有什么卵用的异或性质)
原来是x插入不进去,改变顺序后,d[c] d[c]d[c]插入不进去,也就是说,对于插入不进去的元素,改变插入顺序后,要么还是插入不进去,要么就是插入进去了,同时另一个原来插入的进去的元素插入不进去了,所以,可以插入进去的元素数量一定是固定的。
显然,如果你去掉线性基里面的任意一个数,都会使得原序列里的一些(或一个)数无法通过用线性基里的元素异或得到,所以,每一个元素都是必要的,换句话说,这里面没有多余的元素,所以,这个线性基的元素个数在保持性质1的前提下,一定是最少的。
顺便贴上插入的代码(代码里的ll 都是指longlong):
void add(ll x)
{
for(int i=50;i>=0;i--)
{
if(x&(1ll<<i))//注意,如果i大于31,前面的1的后面一定要加ll
{
if(d[i])x^=d[i];
else
{
d[i]=x;
break;//记得如果插入成功一定要退出
}
}
}
}
如何求最大值
完整的说,是如何求在一个序列中,取若干个数,使得它们的异或和最大。
首先构造出这个序列的线性基,然后从线性基的最高位开始,假如当前的答案异或线性基的这个元素可以变得更大,那么就异或它,答案的初值为0。
代码如下:
ll ans()
{
ll anss=0;
for(int i=50;i>=0;i--)//记得从线性基的最高位开始
if((anss^d[i])>anss)anss^=d[i];
return anss;
}
如何求最小值
显然的,最小值一定是最小的d[i]。(如果让最小的d[i]去异或其它的d[i]一定会让它变得更大,所以它自己就是最小的)
如何求第k小的值
完整的说,应该是——从一个序列中取任意个元素进行异或,求能异或出的所有数字中第k小的那个。
首先,要对这个序列的线性基处理一下,对于每一个d[i] d[i]d[i],枚举j=i to1,如果d[i] (2)的第j位为1,那么d[i]异或d[j−1]。
那么处理完一个线性基之后,应该大致是长这个样子的(x表示0或1):
1xxxx0xxx0x
1xxx0x
1x
求解过程:将k先转成二进制,假如k的第i位为1,ans就异或上线性基中第i ii个元素(注意不是直接异或d[i-1])。
代码如下:
void work()//处理线性基
{
for(int i=1;i<=60;i++)
for(int j=1;j<=i;j++)
if(d[i]&(1<<(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;
}
}
如何判断一个数是否能被当前线性基中的元素异或得到
把它尝试插入进线性基里面去,假如可以插入,说明不能异或得到,假如插不进去,说明能异或得到 (原理然而上面已经讲了)