[模板] 线性基

简介

线性基是一个最小的集合 \(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, 也就是说, 如果有这样的一个线性基

\[A = \begin{bmatrix} 1 & 0 & 1\\ 0 & 0 & 0\\ 0 & 0 & 1\\ \end{bmatrix} \]

它可以被简化为

\[A = \begin{bmatrix} 1 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 1\\ \end{bmatrix} \]

从而使得每一列至多有一个 \(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;
}
posted @ 2019-02-17 17:39  Ubospica  阅读(255)  评论(0编辑  收藏  举报