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} fij∈/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 long
using 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;
}