题目
给你1e6个数,你需要找出有多少子序列,他们的值and起来为0。
思路
设f(s)表示状态为s,s中为1的位一定是1,为0的位可能为1的可选数字的个数,g(s)表示状态s中为1的位的个数。那么可以由容斥原理得到ans=∑220s=0(−1)g(s)∗(2f(s)−1)
核心就在于计算f(s)的值,若暴力计算,需要枚举s的子集复杂度高达320,不可行。考虑dp[i][s]表示低i位为s0,高(19−i)位为s1的数的个数,其中,s0中为1的一定为1,为0的可能为1,s1中为1的一定为1,为0的一定为0。
转移显然,
dp[i][s]={dp[i−1][s], dp[i−1][s]+dp[i−1][s|1<<(i−1)],si=1si=0
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <cassert>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <sstream>
#include <set>
#include <vector>
#include <stack>
#define ALL(x) x.begin(), x.end()
#define INS(x) inserter(x, x,begin())
#define ll long long
#define CLR(x) memset(x, 0, sizeof x)
using namespace std;
const int inf = 0x3f3f3f3f;
const ll MOD = 1e9 + 7;
const int maxn = 1025 * 1025;
const int maxv = 1e3 + 10;
const double eps = 1e-9;
ll dp[21][maxn];
ll Pow(ll a, ll n) {
ll ret = 1;
while(n) {
if(n & 1) ret = ret * a % MOD;
n >>= 1;
a = a * a % MOD;
}
return ret;
}
int main() {
#ifdef LOCAL
freopen("C:\\Users\\Administrator\\Desktop\\in.txt", "r", stdin);
freopen("C:\\Users\\Administrator\\Desktop\\out.txt","w",stdout);
#endif
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
int a;
scanf("%d", &a);
dp[0][a]++;
}
for(int i = 1; i <= 20; i++) {
for(int s = 0; s < 1 << 20; s++) {
dp[i][s] = dp[i-1][s];
if((s >> (i - 1) & 1) == 0) dp[i][s] += dp[i-1][s | (1 << i - 1)];
assert(dp[i][s] >= 0);
}
}
ll ret = 0;
for(int i = 0; i < 1 << 20; i++) {
int num = __builtin_popcount(i);
if(num & 1) ret = (ret + MOD - Pow(2, dp[20][i]) + 1) % MOD;
else ret = (ret + Pow(2, dp[20][i]) - 1 + MOD) % MOD;
}
cout << ret << endl;
return 0;
}