2019牛客暑期多校训练营(第一场)H XOR
登录—专业IT笔试面试备考平台_牛客网
https://ac.nowcoder.com/acm/contest/881/H
题意:给定n个整数,求满足子集异或和为0的子集大小之和。
题解:相当于求每个数出现在子集中的次数之和。
先对n个数求线性基,设线性基大小为r,可以分别计算线性基内数的贡献和线性基外数的贡献
1.线性基外:共n-r个数,枚举每个数x,将线性基外剩余的n-r-1个数任意排列,显然共有 个集合,这些集合再异或x的结果还是能被线性基异或出,所以x的贡献为 。
2.线性基内:枚举每个数x,将所有剩余的n-1个数再求一次线性基,设为B,分两种情况:
(1) x不能被B异或出。那么显然x不能在任意一个集合中出现,x的贡献为0。
(2) x可以被B异或出。此时B的大小必定也为r,因为B已经能表示所有n个数了。那么在除去x和B的情况下,剩余n-r-1个数显然也是任意排列,x贡献为 。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const int maxn = 100000 + 5; const int mod = 1e9 + 7; ll a[maxn]; vector<int> ID; struct LinerBase { int cnt; ll b[65]; void init() { cnt = 0; memset(b, 0, sizeof(b)); } bool Insert(ll x) { for(int i = 63; i >= 0; i--) { if(x & (1LL<<i) ) { if(!b[i]) { b[i] = x; cnt++; return true; } else x ^= b[i]; } } return false; } bool check(ll x) { for(int i = 63; i >= 0; i--) { if(x & (1LL<<i)) x ^= b[i]; } return x==0; } }B1, B2, B3; ll qpow(ll a, ll b) { ll res = 1; while(b) { if(b & 1) res = res * a % mod; a = a * a % mod; b >>= 1; } return res; } int main() { int n; while(~scanf("%d", &n)) { for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); B1.init(); B2.init(); ID.clear(); for(int i = 1; i <= n; i++) { if(!B1.check(a[i])) { B1.Insert(a[i]); ID.push_back(i); } else B2.Insert(a[i]); } int r = B1.cnt; if(r == n) { printf("0\n"); continue; } ll tp = qpow(2, n-r-1); ll ans = (n-r) * tp % mod; for(int i = 0; i < ID.size(); i++) { ll x = a[ID[i]]; for(int j = 0; j < 65; j++) B3.b[j] = B2.b[j]; for(int j = 0; j < ID.size(); j++) { if(j != i) B3.Insert(a[ID[j]]); } if(B3.check(x)) ans = (ans + tp) % mod; } printf("%lld\n", ans); } return 0; }