[模板] 线性基
简介
线性基是一个最小的集合 \(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)<<'\n';
return 0;
}