1. 引入线性基的问题:
- 一些数随意选取,求可以异或出的第k大数。单纯枚举所有情况的话是 O ( 2 n ) O(2^n) O(2n),引入线性基后,可以把时间复杂度降低到 O ( n l o g n ) O(nlogn) O(nlogn)。
2. 什么是线性基&&为什么线性基可以降低时间复杂度:
- 把一堆数以二进制的形式纵向排列,形成了 n ∗ 64 n*64 n∗64 的 01 01 01 矩阵,由于矩阵的秩 r ( A ) ≤ m i n ( n , 64 ) r(A)\leq min(n,64) r(A)≤min(n,64) 。
- 可以证明这些数字组成的矩阵一定可以找到大小小于等于 64 64 64 的 Z 2 {Z}_2 Z2 域中的线性无关组。( 在 Z 2 {Z}_2 Z2 中的加法为异或,乘法为与,可以证明 Z 2 {Z}_2 Z2 是域。)
- 找到线性无关组之后,一个简单的想法就是枚举所有向量的组合,实际上起作用的向量只有64个, O ( 2 64 ) O(2^{64}) O(264) 就可以找到所有的情况了,这样的线性无关组就是线性基。实际上,如果只需要寻找一个数可不可以被构成,可以达到 O ( l o g n ) O(logn) O(logn) 的级别。
3. 如何实现寻找线性基等函数
- 贪心构造出向量组的线性基:
贪心地选取每一位,如果这一位已经有了,就把它异或掉,可以证明到最后一定是一组基。void insert(int x){//插入一个数x for(int i=63;i>=0;i--){ if(x&(1ll<<i)){ if(p[i]) x^=p[i]; else { p[i]^=x; cnt++; break; } } } }
这样构造出来的线性基, p [ i ] p[i] p[i] 一定是表示首位为 1 1 1 的基,而且不会重复,插入单个元素复杂度 O ( l o g n ) O(logn) O(logn)。 - 寻找最大数:
从高位到低位枚举,贪心选取最高位一定是对的。int askmx(){ int x=0; for(int i=63;i>=0;i--){ if((x^p[i])>x) x^=p[i]; } return x; }
- 寻找最小数:
一般来说,最小值就是贪心构造出来的线性基的某个值,但是需要考虑一个特殊情况——几个数异或起来变成了 0 0 0。这种情况只需要判断插入的数字的数量和这些数字构造出的基的数量,如果有数字没有成为基,那么它一定可以被其它基线性表出,这种情况会异或出0。int askmn(){ int mn=inf; if(cnt==0||n>cnt) return 0; else{ for(int i=0;i<=64;i++){ if(p[i]) mn=min(mn,p[i]); } return mn; } }
- 查询是否可以构造出某个数:
从大到小异或,这样如果最后异或为 0 0 0,代表可以线性表出,不为零代表不行。bool ask(int x){//查询是否有一个数字 for(int i=63;i>=0;i--){ if(x&(1ll<<i)){ if(p[i]) x^=p[i]; } } return (x==0); }
- 求第
k
k
k 大数:
原来的线性基不满足求第 k k k 大的要求,不满足行阶梯型,即不会有两个线性基共享其中一个的最高位,如 10110 10110 10110 和 00100 00100 00100 ,不是一组完美的线性基。需要通过高斯消元求阶梯形。void rebuild() { cnt=0; for(int i=63;i>=0;i--) for(int j=i-1;j>=0;j--) if(p[i]&(1LL<<j)) p[i]^=p[j]; for(int i=0;i<=63;i++) if(p[i]) d[cnt++]=p[i]; } int kth(int k) { if(k>=(1LL<<cnt)) return -1; int ans=0; for(int i=63;i>=0;i--) if(k&(1LL<<i)) ans^=d[i]; return ans; }
求完之后,可以直接贪心选取第k个数。
这里还需要判断 0 0 0 的情况,如果会凑出 0 0 0 ,查询位置要递推 1 1 1。for(int i=0;i<64;i++) p[i]=0; int n;cin>>n; for(int i=1;i<=n;i++){ int x;cin>>x; insert(x); } rebuild(); int m;cin>>m; if(n>cnt) zero=1; for(int i=1;i<=m;i++){ int q;cin>>q; if(n>cnt&&q==1) cout<<0<<endl; else cout<<kth(q-zero)<<endl; }
4. 线性基一些性质:
- Q: 有 n n n 个元素,每个元素有个序号和一个值,一个元素可以选择当且尽当其序号与已选元素序号的异或和不为 0 0 0,求你可选择的元素值和的最大值
- A: 贪心地将最大值元素的序号塞入线性基中,如果插入失败,就不选择这个元素。这样插入的结果一定是最大值。
- 性质:如果一个元素不能插入线性基中(可以被其它线性基线性表出),一定可以只删掉一个元素,把它插入线性基。
5. 一道测试题:
- https://codeforces.com/gym/105336/attachments
- 将 a i a_i ai 和 b i b_i bi 的值异或起来,插入线性基,然后按位分类讨论贪心即可。