题目大意:给定一个序列,选择其中的一部分数,他们相互&的和为0。
f[i]数组表示有多少个数相互&结果都是i,就是这个数i是多少个数的子集
可以先枚举每位,再枚举每个数字,如果这个数字当前位为1,那么当前位的值可以作为
删去这个位的值。这样按位数枚举,每个位都由前一个位推过,不会重复,而且枚举
位数和每个数保证了完全性。
求出f之后,dp[i]表示大于等于i个1的数的个数,枚举每个数(注意这里可以为0,
含义为答案可能为的数),再枚举每个位置,算出这个数的为1的个数tot,
那么dp[tot]加上2^(f[i])-1,也就是所有可以&出这个位数的数每个数选或不选都可以
那么就是2的幂次个,去掉都不选的情况,最后利用容斥原理,
ans = dp[0]-dp[1]+dp[2]……
因为每个数都是互相不重复的,也保证了算法的正确性。
下面代码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define mode 1000000007
#define N 1000006
using namespace std;
typedef long long ll;
ll f[N];
ll ho[N];
ll dp[25];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1;i <= n;i++)
{
int x;
scanf("%d", &x);
f[x]++;
}
ho[0] = 1;
for(int i = 1;i <= N;i++)
{
ho[i] = ho[i-1]*2%mode;
}
for(int i = 0;i <= 20;i++)
{
for(int j = 1;j <= N;j++)
{
if((1<<i)&j)
{
f[j^(1<<i)] += f[j];
f[j] %= mode;
}
}
}
for(int i = 0;i <= N;i++)
{
ll tot = 0;
for(int j = 0;j <= 20;j++)
{
if((1<<j)&i)
{
tot++;
}
}
dp[tot] += ((ho[f[i]]-1)%mode+mode)%mode;
dp[tot] %= mode;
}
ll ans = 0;
for(int i = 0;i <= 20;i++)
{
if(i%2)
{
ans -= dp[i];
ans %= mode;
}else
{
ans += dp[i];
ans %= mode;
}
}
printf("%I64d",(ans%mode+mode)%mode);
return 0;
}