<span>2019牛客暑期多校训练营(第一场)H 线性基+计算贡献</span>

题意

给n个整数,求满足子集异或和为0的子集大小之和。

分析

将问题转化为求每个元素的贡献次数之和。

先对n个数求线性基,设线性基大小为r,即插入线性基的数字个数为r,可以分别计算线性基内数的贡献和线性基外的数的贡献

  • 线性基外:共n-r个数,枚举每个数x,它可以和将线性基外剩余的n-r-1个数同时存在一个集合内,显然共有\(2^{n-r-1}\)个集合,所以x的贡献为\(2^{n-r-1}\)
  • 线性基内:枚举每个数x,将剩余的n-1个数再求一次线性基,设为B,分两种情况:
    • x不能被B异或出。那么显然x不能在任意一个集合中出现,x的贡献为0。
    • x可以被B异或出。此时B的大小必定也为r,因为B已经能表示所有n个数了。那么在除去x和B的情况下,x可以和剩余的n-r-1个数同时存在一个集合内,x的贡献为\(2^{n-r-1}\)

Code

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define ll long long
using namespace std;
const int inf=1e9;
const int mod=1e9+7;
const int maxn=1e5+10;
int n;
ll a[maxn];
struct node{
	ll p[65];
	int cnt;
	void clear(){
		cnt=0;
		memset(p,0,sizeof(p));
	}
	bool insert(ll x){
		for(int i=60;i>=0;i--){
			if(x&(1ll<<i)){
				if(!p[i]){
					p[i]=x;cnt++;
					return 1;
				}
				x^=p[i];
			}
		}
		return 0;
	}
};
node x,y,z;
ll ksm(ll a,ll b){
	ll ret=1;
	while(b){
		if(b&1) ret=ret*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ret;
}
int main(){
	//ios::sync_with_stdio(false);
	//freopen("in","r",stdin);
	while(~scanf("%d",&n)){
		x.clear();y.clear();z.clear();
		for(int i=1;i<=n;i++){
			scanf("%lld",&a[i]);
		}
		vector<ll>q,res;
		for(int i=1;i<=n;i++){
			if(x.insert(a[i])){
				q.pb(a[i]);
			}else res.pb(a[i]);
		}
		if(n==x.cnt){
			puts("0");
			continue;
		}
		ll ans=1ll*(n-x.cnt)*ksm(2,n-x.cnt-1)%mod;
		for(ll val:res){
			y.insert(val);
		}
		for(ll i:q){
			z=y;
			for(ll j:q){
				if(i==j) continue;
				z.insert(j);
			}
			if(!z.insert(i)) ans=(ans+ksm(2,n-x.cnt-1))%mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
全部评论

相关推荐

10-05 23:02
东北大学 Java
我说句实话啊:那时候看三个月培训班视频,随便做个项目背点八股,都能说3 40w是侮辱价
点赞 评论 收藏
分享
邮小鼠:粤嵌的项目水的要死 来我们学校带过课程实习 项目名字是车机终端 实际上就是写了了个gui 还是老师把代码发给你你改改的那种
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务