P r o b l e m \mathrm{Problem} Problem
给出一个长度为n的序列 a 1 , a 2 . . . a n a_1,a_2...a_n a1?,a2?...an?。
求构造出一个序列 i 1 ≤ i 2 ≤ . . . ≤ i k ( 1 ≤ k ≤ n ) i_1 \le i_2 \le ... \le i_k(1\le{k}\le{n}) i1?≤i2?≤...≤ik?(1≤k≤n)使得 a i 1 a n d a i 2 a n d . . . a n d a i k = 0 a_{i_1}\mathrm{\ and\ }a_{i_2}\mathrm{\ and\ }...\mathrm{\ and}\ a_{i_k}=0 ai1?? and ai2?? and ... and aik??=0
求方案数模 1 0 9 + 7 10^9+7 109+7 。
也就是从 { a i } \{a_i\} { ai?} 里面选出一个非空子集使这些数按位与起来为 0 0 0.
S o l u t i o n \mathrm{Solution} Solution
前置芝士: 求 n n n个数中,任意 i i i 有几个数满足 a i a n d i = i a_i\ \mathrm{and\ } i=i ai? and i=i。
考虑状压: f i ∑ j ? i f i + 2 j f_i\sum _{j?i}f_{i+2^j} fi?j∈/?i∑?fi+2j?
这样我们发现会有重复,考虑为什么会重复。
那么怎么办呢?首先想到的容斥,即:加上新增 1 1 1 的,减去新增 2 2 2 的…
但是我们神奇的发现将 i i i 和 j j j 的枚举顺序调换一下,就神奇的不重复了。
因此我们得到了一个做状压计数的结论:
- 若某一个数想要求得以它为子集的数的所有贡献,我们只需要调换枚举顺序,累计每一次新增 1 1 1 的贡献即可。
回到正题,那么这题运用上述结论就十分好解决了。
我们设 f i f_i fi? 表示and值为 i i i 的答案,我们发现十分难搞。但是令 f i f_i fi?表示and值子集为 i i i 的方案数,我们发现我们可以用 f i f_i fi? 减去以 i i i 的贡献即为正确答案。
类比上面的前置芝士,我们发现我们只要将加号改成减号就可以得到答案。
那么我们才能得到 f i f_i fi? 呢,我们发现只要有 n n n 个数的子集存在 i i i,答案即为: 2 n ? 1 2^n-1 2n?1
因此我们先令上述统计个数一遍,在变换成方案数以后再相减一遍即可。
C o d e \mathrm{Code} Code
#include <bits/stdc++.h>
#define int long longusing namespace std;
const int N = 3e6;
const int P = 1e9 + 7;int n, S;
int f[N];int read(void)
{
int s = 0, w = 0; char c = getchar();while (!isdigit(c)) w |= c == '-', c = getchar();while (isdigit(c)) s = s*10+c-48, c = getchar();return w ? -s : s;
}int power(int a, int b) {
int res = 1;while (b > 0) {
if (b & 1) res = res * a % P;a = a * a % P, b >>= 1;}return res;
}void DP(int x)
{
for (int j=0;j<=19;++j){
for (int i=S-1;i>=0;--i)if (((i >> j) & 1) == 0) f[i] = (f[i] + f[i | 1 << j] * x) % P;}return;
}signed main(void)
{
n = read(), S = (1 << 20) - 1;for (int i=1;i<=n;++i) f[read()] ++;DP(1);for (int i=0;i<=S;++i) f[i] = power(2, f[i]) - 1;DP(-1); cout << (f[0] + P) % P << endl;return 0;
}