线性基
今天写到线性基,然后谈谈这个吧
首先,线性基是用来求最大异或和,对于一组数,如果我们要求他最大异或和的话,最朴素的想法
把每种情况列举然后搜索(或者用循环,1-2的n次方),所以,如果数据一大,它的时间复杂度就爆了(1024的五次方QAQ)
所以线性基为了解决这个问题,起到了很好的作用
我们知道,一组数的异或和,是有规律的啊,比如说,你要求
000011001
100100101
111001001001
111010101010
这四个数字的异或和,你会去一个个枚举吗?当然不会,因为首先你会发现
我们肯定要选第三个数字,因为他的最高位数很大,我们只要把他异或了,绝对比没有异或更优
也就是我们找到一个拥有最高位的1的数字,然后如果 ans的这个位是0的话,ans^这个数字>ans,我们肯定要选
因为高位上的一个1比后面全是1都有用!!!!
但是又产生了矛盾,我们把最高位1的数字排列??那你怎么确定哪一个最优呢?而且你怎么知道异或哪个?这是一个问题。
这个时候需要对异或有一个充分的了解。**x1^x2=x3,可以推出x1 ^x3=x2.**当然,你无论怎么换都是成立的,这个如何证明?
首先,我们逐位处理吧,1^1=0, 0 ^1=1,0 ^0=0,发现没,这样是成立的,如果把左边的移到右边,右边的移到左边也是成立的,这是基于每一位二进制位而言的,显然,每一位之间都不会互相干扰,所以成立
线性基,是一组集合,可以用来保存输入的那些数字里面信息,在保证每一个p[i]只有一个的情况下能够组合出所有的答案,
也就是说我们答案总数保持不变,用这些线性基都可以异或过来,只是改变了这个数字存储的值而已。
然后线性基,
然后线性基对于之前的那个问题的处理方式是这样的,我们把p[i]定义为最高为在i位的基,对于输入进来的一个数字,我们从最高位开始扫:
1.如果当前位置是1,没有这个基,我们就存储他到集合里面
2.如果当前位置是1,而且基存在,那我们为了不让线性基产生冲突,我们可以直接异或这个位置上的1,然后得到一个更小的基(如果还是2条件就逐渐变小,因为我们题目条件,并不会出现没有这个基的情况,50个数字嘛,然后2的50次方,必定会走到1的情况)。有人会质疑,那我那个后来的数字去哪里了??最后怎么在集合里面查找到他?这时候就要依靠上面所推得的结论,因为这个x1^x2=x3,最后我们查询这个x1的时候,就可以用x2 ^x3来查,所以并不会出现所谓所给数据在线性基里面找不到的情况,必定使得所以的原数和他们的异或都可以被线性基里面的数字给表示出来。
所以最后根据贪心法,我们从大到小一个个异或这个基,可以得出最后的答案。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[1000010];
ll p[1000001];
void work(ll x){
for(ll i=62;i>=0;i--){
if(!(x>>i)){
continue;
}
if(!p[i]){
p[i]=x;
break;
}
x^=p[i];
}
}
int main(){
ll ans=0;
ll n;
cin>>n;
for(ll i=1;i<=n;i++){
cin>>a[i];
work(a[i]);
}
for(ll i=62;i>=0;i--){
if((ans^p[i])>ans){
ans^=p[i];
}
}
cout<<ans;
return 0;
}
代码实现起来并不算太难,代码也很简洁明了,主要的这个还是运用了异或神奇的结论,才可以变成如此的优美啊!