Description
定义一个非空集合是合法的,当且仅当它满足以下两个条件。
- 1、集合内所有元素 and 和为 0 。
- 2、它的非空子集中仅有它本身满足
1 。
给出一个集合
S
,求它的合法非空子集数。
Solution
设
f(S)=⋀a∈Sa
Sa
为集合
S
删去那么就有答案为:
ans=∑S[(f(Sa)=0)∧⋀a∈S(f(Sa)≠0)]
右边这部分是可以容斥的
[⋀a∈S(f(Sa)≠0)]=∑T∈S[⋀a∈T(f(Sa)=0)](−1)|T|
还有
[⋀a∈T(f(Sa)=0)]=[(⋁a∈Tf(Sa))=0]
因为左式相当与把所有
f(Sa)
按位或起来值是否为
0
。(类似于补集转化之类的吧)。 所以就有
这样就可以DP了。
需要记录上式前后两个值。
设 dpi,j,k 表示考虑前 i 个数,
每次转移考虑是否在
- 若不选入
S 与 T :dpi−1,j,k→dpi,j,k - 若选入
S
但不选入
T : dpi−1,j,k→dpi,j∧x,k∧x - 若选入
S
与
T : −dpi−1,j,k→dpi,j∧x,k∧x∨j 可以再所有数之前加一个 1023 ,这样便于初始化,也不会对答案造成影响。
因为是在枚举集合中枚举子集,所以时间复杂度是 O(n310) 的。#include <bits/stdc++.h> using namespace std; const int N = 1030; const int MOD = 1000000007; inline char get(void) { static char buf[100000], *S = buf, *T = buf; if (S == T) { T = (S = buf) + fread(buf, 1, 100000, stdin); if (S == T) return EOF; } return *S++; } inline void read(int &x) { static char c; x = 0; for (c = get(); c < '0' || c > '9'; c = get()); for (; c >= '0' && c <= '9'; c = get()) x = x * 10 + c - '0'; } int dp[N][N], dp1[N][N]; int n; int a[N]; inline void Add(int &x, int a) { x += a; while (x >= MOD) x -= MOD; } int main(void) { read(n); for (int i = 1; i <= n; i++) read(a[i]); dp1[1023][1023] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < 1024; j++) { for (int S = j; S; S = (S - 1) & j) dp[S][j] = 0; dp[0][j] = 0; } for (int j = 0; j < 1024; j++) { for (int S = j; S; S = (S - 1) & j) { if (!dp1[S][j]) continue; Add(dp[S][j], dp1[S][j]); Add(dp[S & a[i]][j & a[i]], dp1[S][j]); Add(dp[S & a[i]][j & a[i] | S], MOD - dp1[S][j]); } if (!dp1[0][j]) continue; Add(dp[0][j], dp1[0][j]); Add(dp[0][j & a[i]], dp1[0][j]); Add(dp[0][j & a[i]], MOD - dp1[0][j]); } for (int j = 0; j < 1024; j++) { for (int S = j; S; S = (S - 1) & j) dp1[S][j] = dp[S][j]; dp1[0][j] = dp[0][j]; } } printf("%d\n", dp[0][0]); }
- 若选入
S
但不选入