线性基的性质:
1,原数集中的数字异或出来的值域与线性基中的元素以后出来的值域相等。
2,线性基中没有异或和为零的非空子集。
3,线性基中的选取元素的每一种方案,都对应一个异或值,不存在多种选取方案对应同一个异或值的情况。
4,线性基二进制最高位互不相同。
5,线性基中元素互相异或,异或集合不变。
6,线性基是满足以上性质的最小集合。
线性基是通过原数集的某些元素构成的。
模板:
struct L_B{
LL d[65],p[65];
int cnt;
L_B()
{
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
// /// 1e18以内的数都适用.
// bool Insert(LL val)
// {
// for (int i=60;i>=0;i--)
// if (val&(1LL<<i))
// {
// if (!d[i])
// {
// d[i]=val;
// break;
// }
// val^=d[i];
// }
// return val>0;/// 可判断val是否存在于线性基当中.
// }
///将值x插入线性基
void Insert(LL x){
for(int i=60;i>=0;i--){
if(x&(1LL<<i)){
if(!d[i]){
d[i]=x;
return ;
}
else x^=d[i];
}
}
}
///检查该值x是否在线性基里面
bool check(LL x){
for(int i=31;i>=0;i--){
if(x&(1LL<<i)){
if(!d[i]) return 0;
else x^=d[i];
}
}
return 1;
}
LL query_max()
{
LL ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
LL query_min() /// 应该预先判断能否是0的情况
{
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
void rebuild() /// 用于求第k小值.需要先进行独立预处理
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1LL<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
LL kthquery(LL k)/// 注意判断原序列异或出0的情况, 此时应该将k -- 在进行后面的操作.
{
LL ret=0;
if (k>=(1LL<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1LL<<i))
ret^=p[i];
return ret;
}
LL& operator [](int x)
{
return d[x];
}
}
L_B Merge(const L_B &n1,const L_B &n2) /// 把n2这个线性基插入到当前这个线性基n1中.
{
L_B ret=n1;
for (int i=60;i>=0;i--)
if (n2.d[i])
ret.Insert(n2.d[i]);
return ret;
}
题目:H-XOR
题意:给定n个整数,求满足子集异或和为0的子集大小之和。
题解:相当于求每个数出现在子集中的次数之和。
先求n的线性基(秩为r),然后对于线性基外的每个点x,它有2^(n-r-1)种子集(除开x),每一个子集与x异或得到的值都能在线性基那里找到唯一组异或值与其相同的方案。显然点贡献了2^(n-r-1),这样的点有n-r个,最后对于线性基外的点,总贡献为 (n-r)*2^(n-r-1)。
对于线性基内的点x,我们求(n-1)的点的线性基(除开x),假设x能被此时的线性基表示,说明此点为线性基的外点,并且此时的线性基内的元素还是r,那么这就回到了前面的计算方法了,此时这个点的贡献为 2^(n-r-1)。
我们不同每次求(n-1)的线性基,只需预处理(n-r)的线性基,然后每次加入(r-1)个元素进去。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
const LL mod=1e9+7;
struct L_B{
int cnt;
LL d[65];
void init(){
cnt=0;
memset(d,0,sizeof(d));
}
bool Insert(LL x){
for(int i=63;i>=0;i--){
if(x&(1LL<<i)){
if(!d[i]){
d[i]=x;cnt++;return true;
}
else x^=d[i];
}
}
return false;
}
bool check(LL x){
for(int i=63;i>=0;i--){
if(x&(1LL<<i)) x^=d[i];
}
return x==0;
}
}B1,B2,B3;
LL qpow(LL x,LL n)
{
LL res=1;
while(n)
{
if(n&1) res=res*x%mod;
x=x*x%mod;
n>>=1;
}
return res;
}
LL a[N];
vector<int> vec;
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
B1.init();B2.init();vec.clear();
///计算前n个元素的线性基
for(int i=1;i<=n;i++){
if(!B1.check(a[i])){
B1.Insert(a[i]);
vec.push_back(i);
}
else B2.Insert(a[i]);///计算n-r的线性基
}
int r=B1.cnt; ///线性基内需要用到的元素个数
if(r==n){
puts("0");continue;
}
LL item=qpow(2,n-r-1);
LL ans=(n-r)*item%mod; ///求线性基外的点的贡献
for(int i=0;i<vec.size();i++)
{
LL x=a[vec[i]];
for(int j=0;j<=65;j++)
B3.d[j]=B2.d[j];
for(int j=0;j<vec.size();j++){ ///求n-1个元素的线性基
if(j!=i) B3.Insert(a[vec[j]]);
}
if(B3.check(x))///如果x在线性基内
ans=(ans+item)%mod;
}
printf("%lld\n",ans);
}
return 0;
}