简介
线性基是一个最小的集合 (S = {p_i}, i in { 0, 1, cdots ,n-1 }), (p_i) 为非 (0) 二进制数, 且每个数在异或意义下线性无关. 将 (n) 称作线性基的秩.
它可以通过异或唯一的表示出 (T = {a_i}, i in { 0, 1, cdots ,m-1 }) 中的每一个值. 利用线性代数的知识可以知道 (m = 2^n).
也就是说, (S) 和 (T) 在异或意义下张成的空间等价.
为了便于维护和查询, 规定 (p_i in S) 的二进制最高位为第 (i) 位, 值为1.
构造
根据定义构造即可. rk
表示线性基的秩, 即 (S) 集合的大小; zerofl
表示给定的数能否表示出 (0).
时间复杂度 (O(log v)), 其中(v)为值域大小.
const int l2sz=70,lbnd=52;
ll lbase[l2sz],rk=0;
void insert(ll v){
repdo(i,lbnd,0){
if(((v>>i)&1)==0)continue;
if(lbase[i])v^=lbase[i];
else{lbase[i]=v,++rk;return;}
}
zerofl=1;
}
应用
能否表示
类似构造, 如果某次异或后为0则能表示; 否则不能.
求最大异或值
对于每一位, 如果异或之后变大就异或.
正确性显然.
ll qmax(){
ll res=0;
repdo(i,lbnd,0)if((res^lbase[i])>res)res=res^lbase[i];
return res;
}
最小异或值
如果能表示出0则为0; 否则, 显然数组中最小非0值为最小异或值.
ll qmin(){
if(zerofl)return 0;
rep(i,0,lbnd)if(lbase[i])return lbase[i];
return -1;
}
简化线性基
类似高斯消元, 容易发现可以使线性基在每一位最多只有一个 1
, 也就是说, 如果有这样的一个线性基
它可以被简化为
从而使得每一列至多有一个 (1).
实现上, 只需将每一行用前面的行异或消去即可.
void reduce(){
rep(i,0,lbnd){
rep(j,0,i-1){
if((lbase[i]>>j)&1)lbase[i]^=lbase[j];
}
if(lbase[i])rbase[++pr]=lbase[i];
}
}
第k大异或值
首先简化线性基.
现在, 对于每一个非空的行 (i), 这一行的值 (p_i) 都是 (i) 位为 (1) 的最小的值.
由于这些行可以任意组合, 对于 (k) 的每一个为 (1) 的位 (i), 把答案异或上从小到大第 (i) 个非零的行即可.
ll qkmin(ll k){
k-=zerofl;
if(k==0)return 0;
if(k>=(1ll<<(pr+1)))return -1;
ll res=0;
rep(i,0,pr){
if((k>>i)&1)res^=rbase[i];
}
return res;
}
表示出k的方案数
首先, 如果线性基不能表示出 (k), 那么答案为 (0);
否则, 考虑没有加入线性基的数的个数 (n') (即 (总数字个数 - rank)). 任意选择这 (n') 个数, 并加入和选择的数异或和为 (0) 的数, 总方案数即为 (2^{n'}).
(0) 是一个特例. 由于线性基中的数不能表示出 (0), 方案数为 (2^{n'} - 1).
所有能表示出的数之和
所有能表示出的数的个数为 (2^n - 1). 如果可以表示出 (0) (zerofl == 1
), 则为 (2^n).
显然每一位可以分开考虑.
对于从小到大第 (i) 位 (从 (0) 开始), 它对答案的贡献为 (2^i cdot cnt), 其中 (cnt) 为这一位为 (1) 的数的出现次数.
考虑如何求 (cnt).
对于第 (i) 位, 如果线性基上的 (n) 个数第 (i) 为均为 (0), 那么能表示出的数的这一位都必为 (0), 则 (cnt) 为 (0);
否则, 这一位可以为 (1), 而其他位可以由线性基上的数任意组合, 共 (2^{n-1}) 种, 即 (cnt = 2^{n-1}).
代码:
ll getsum(){
ll ans=0;
rep(i,0,62){
int fl=0;
rep(j,0,62)fl|=((lbase[j]>>(ll)i)&1);
if(fl)ans+=(1ll<<(rk-1))*(1ll<<i);
}
return ans;
}
总的代码
loj114: k大异或和
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define rep(i,l,r) for(register int i=(l);i<=(r);++i)
#define repdo(i,l,r) for(register int i=(l);i>=(r);--i)
#define il inline
typedef long long ll;
typedef double db;
//---------------------------------------
const int lsz=70,lbnd=52;
int n,m;
ll lbase[lsz],rk=0,zerofl=0;
ll rbase[lsz],pr=-1;//real base
void insert(ll v){
repdo(i,lbnd,0){
if(((v>>i)&1)==0)continue;
if(lbase[i])v^=lbase[i];
else{lbase[i]=v,++rk;return;}
}
zerofl=1;
}
ll qmax(){
ll res=0;
repdo(i,lbnd,0)if((res^lbase[i])>res)res=res^lbase[i];
return res;
}
ll qmin(){
if(zerofl)return 0;
rep(i,0,lbnd)if(lbase[i])return lbase[i];
return -1;
}
void reduce(){
rep(i,0,lbnd){
rep(j,0,i-1){
if((lbase[i]>>j)&1)lbase[i]^=lbase[j];
}
if(lbase[i])rbase[++pr]=lbase[i];
}
}
ll qkmin(ll k){
k-=zerofl;
if(k==0)return 0;
if(k>=(1ll<<(pr+1)))return -1;
ll res=0;
rep(i,0,pr){
if((k>>i)&1)res^=rbase[i];
}
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
ll a;
cin>>n;
rep(i,1,n)cin>>a,insert(a);
reduce();
cin>>m;
rep(i,1,m)cin>>a,cout<<qkmin(a)<<'
';
return 0;
}