线性基讲解
首先,什么是线性基:
线性基是一个数的集合,任意一个序列都有至少提个线性基。
有一组数a1,a2...an和线性基d1,d2...,dm,di表示**最高位1在第i位的数**。
线性基的作用
由于线性基值域与原数列值域相同的特点,可以用它来维护异或和。
线性基的性质
线性基有以下三大性质:
1.原序列里面的任意一个数都可以由线性基里面的一些数异或得到。
2.线性基里面的任意一些数异或起来都不能得到0。
3.线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的。
线性基的插入代码
bool add(ll x) { for(int i=30;i>=0;i--) { if((x>>i)&1) { if(d[i]) x^=d[i]; else { d[i]=x; return 1; } } } return 0; }
这样这个d数组的性质就是若d[i]不为0,则(d[i])2的第d[i]的第i+1位为1,并且没有更高位为1了。
性质1的证明
如果有了前面讲的,设原数列有一个数x,我们尝试用它来构造线性基,这样就会有两种可能。
1.不能成功插入线性基。
2.可以成功插入线性基。
接下来分两类讨论性质1的证明
不能成功插入线性基
如果是不能成功插入的话,那么一定是与性质2相悖,所以就可以显然得出这个式子: x^d[a]^d[b]^...=0
则可以推出 d[a]^d[b]^...=x
所以如果x不能插入线性基,一定是前面以及有一些数的异或和为x。满足性质1。
能够成功插入线性基
假设这个数插入到了第i个位置,则可以得出 x^d[a]^d[b]^...=d[i]
能推出 d[i]^d[a]^d[b]^...=x
所以这种情况也能满足性质1。
性质1得证。
性质2的证明
证法显然,但是还是说一下吧。
反证法:假设d[a]^d[b]^...d[i]=0(假设d[i]比d[a]等晚入线性基)
则可以推出d[a]^d[b]^...=d[i],而这样根据入线性基方案,d[i]不可能入线性基,所以假设不成立。
所以性质2得证。
性质3的证明
还是分类讨论
如果序列中所有元素都被插入线性基中
由于所有元素都要入线性基,故不管用什么顺序都是一样的,所以就能保证最优性,即满足性质3.
如果有元素没有被插入线性基中
设这个元素为x,则一定满足一个d[a]^d[b]^d[c]=x的式子,则我们改变一些顺序。(由于a,b,c都是对称的,则只用考虑c即可)
我们可以得到这个式子d[a]^d[b]^x=d[c]
所以d[c] 就无法插入线性基了,所以总数还是一样的,所以改变顺序并不会改变插入数量,所以数量是一定的。
性质3得证。
所以来看一道线性基的题目
https://www.luogu.com.cn/problem/P4301
这道题就是一道经典线性基的题目
解析:这道题怎么做呢?这道题和Nim游戏很像,唯一的区别就是第一次和第二次可以随便取。所以我们只要解决这唯一的不同点也就解决这个问题了。
这道题要让取完之后无论怎么拿都不会出现异或和为0的情况,所以我们正好可以用线性基来维护,但是我们的入线性基的顺序是什么呢?
这里有个贪心,那就是说a^b^c...<=a+b+c+...,这个的证法显然我就不说了。
所以我们根据这个贪心,可以确定是要从大到小的顺序依次考虑入线性基,所以我们一开始就要排个序。
根据题意,如果入线性基成功,那就没有事了,但是如果失败就要统计上ans,所以单独写一个入线性基add的时候要定义的是bool类型的函数,以便判断入线性基是否成功。
最后再考虑-1 的事情,可以构造出一种情况,无论如何先手也不会输,那就是取得只剩1堆,之后先手就直接去完这剩下的一堆即可,所以是不可能输出 -1 的。
所以正解代码如下:
#include<bits/stdc++.h> #define ll long long using namespace std; const int NR=105; int a[NR]; int d[NR]; ll ans; bool add(ll x) { for(int i=30;i>=0;i--) { if((x>>i)&1) { if(d[i]) x^=d[i]; else { d[i]=x; return 1; } } } return 0; } bool cmp(int x,int y) { return x>y; } int read() { int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*f; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); int n=read(); for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++) { bool flag=add(a[i]); if(!flag) ans+=a[i]; } printf("%lld",ans); return 0; }