一、线性基浅谈
在了解线性基之前,要简单理解什么是基。在线性代数中,基又称为基地,是刻画向量的工具。对于基底的元素我们称为基向量,向量空间的任意一个元素都可以唯一表示成为基向量的线性组合。同样,线性基也是一种基,它是一种特殊的基,一般用来求异或问题,所以在这里就先按照解决异或问题来讲述。
这种特殊的基是由$64$个数字或$32$个数字组成,具体是由几个数字组成要取决于,原题中给出的数字集合在二进制下的最高位,解释一下:若原题中的答案在$int$范围内,则运用$32$位线性基足以,若答案在$long long$范围内,就一定要用$64$线性基来解决,高精度另当别论。
现在对于$n$个数字(下面称这$n$个数为原数集):$a_1,a_2 ……a_n$,构建线性基,先不提线性基是如何构建出来的,先说一说线性基有什么性质:
1.原数集中的任意一个数字都能够通过线性基中的元素异或出来(这与线性基的构建方式有关)。
2.原数集中的数字异或出来的值域与线性基中的元素以后出来的值域相等(通过上一条性质可知)。
3.线性基中没有异或和为零的非空子集:现在假设存在这样一个子集使得$b_1,b_2……b_x$的异或和为零,那么根据异或的性质能得出:$b_1=b_2oplus b_3oplus……oplus b_x$,既然$b_1$已经能用除他之外的线性基元素表示出来,我们便没有必要再将$b_1$放在线性集中。
4.线性基中的选取元素的每一种方案,都对应一个异或值,不存在多种选取方案对应同一个异或值的情况:现在假设存在这种情况,那么我们就会存在一个非空子集的异或值为零,这与上一条性质矛盾。
5.线性基是满足以上性质的最小集合,即线性基中不存在任何一个多余的元素。
二、线性基的构造
(在下面描述中,称线性基对应的第$i$位元素为$place_i$)对于每一个数字$num$,从高位向低位扫,扫到第$x$位为$1$时,若当前$place_x$有数字,则将$num$异或上$place_x$,即使$num=numoplus place_x$,并向下继续扫,若$place_x$没有数字,则当前$num$就使$place_x$,即$place_x=num$。
下面有两种写法:第一种写法是按照上方描述的过程写的,第二种是以上方描述的过程为思想进行改进的,是所有$n$个数字同时插入。
void insert(long long x) { for(int i=63;~i;i--) if((x>>i)&1ll) { if(!place[i]) {place[i]=x;return;} else x^=place[i]; } }
void insert(int *num) { for(int j=63;~j;j--) { int p=0; for(int i=1;(!p)&&i<=n;i++) if((!v[i])&&((num[i]>>j)&1ll)) p=i; if(!p) {place[j]=0;continue;} v[p]=true,place[j]=num[p]; for(int i=1;i<=n;i++) if(i!=p&&((num[i]>>j)&1ll)) num[i]^=num[p]; } }
三、线性基应用
1.询问数字$x$是否在当前线性基异或集合中:
我们对于数字$x$从高位向低位扫,若数字$x$在二进制下的第$i$位为$1$,则异或上$place_i $,若数字$x$最后异或出来等于零,则数字$x$在当前线性基的异或集合中,反之则不在。
bool check(long long x) { for(int i=63;~i;i--) if((x>>i)&1ll) x^=place[i]; return x==0; }
2.将线性基$A$和线性基$B$合并:
我们将线性基$A$中的每一个数字都依次插入到线性基$B$中即可。
struct Base { long long place[N]; void insert(long long x) { for(int i=63;~i;i--) if((x>>i)&1ll) { if(!place[i]) {place[i]=x;return;} else x^=place[i]; } } }; void merge(Base A,Base &B) { for(int i=63;~i;i--) if(A.place[i]) B.insert(A.place[i]); }
3.用线性基求当前数集能异或出来的最大数字:
我们对于当前数集构造线性基,因为二进制下高位的$1$对于答案的贡献要比下面所有的位数都为$1$的贡献还要大,所以我们可以进行贪心,若答案异或上当前位数上的数字比答案要大,则异或。
long long mx() { long long ans=0; for(int i=63;~i;i--) if((ans^place[i])>place) ans^=place[i]; return ans; }
4.用线性基求当前数集能异或出来的最小数字:
我们对于当前数集构造线性基,直接取出最后一位不是零的数字即可,为什么?仔细想想挺简单的。
long long mn() { for(int i=0;i<=63;i++) if(place[i]) return place[i]; return -1; }