P6442 [COCI2011-2012#6] KOŠARE
算法
子集反演, 容斥原理, DP
.
\(\tt{Solution}\)
考虑暴力状压, 令 \(f_{i, \mathbb{S}}\) 表示枚举到第 \(i\) 个箱子时, 至少放了一次玩具构成的集合为 \(\mathbb{S}\) 的方案数. 转移时枚举第 \(i\) 个箱子取不取即可.
想法很好, 但是会超时.
直接枚举不好做, 考虑放宽限制, 最后容斥回去.
设 \(f_{\mathbb{S}}\) 表示包含的玩具是 \(\mathbb{S}\) 的子集时的方案数, 一个非常典的 SOS DP, 预处理一下就行.
再令 \(g_{\mathbb{S}}\) 为钦定 \(\mathbb{S}\) 中的玩具不选, 其余玩具随意的方案数, 那么 \(g_{\mathbb{S}} = 2^{f_{\mathbb{U} \setminus \mathbb{S}}} - 1\).
答案即为 \(\displaystyle \sum_{i = 0}^{2^m - 1} (-1)^{\lvert i \rvert} g_i\).
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
std;
constexpr int N = 1e6 + 10, M = 20, mod = 1e9 + 7;
int n, m, f[1 << M], pw[N];
void init() {
scanf("%lld %lld", &n, &m);
pw[0] = 1;
for (int i = 1, k; i <= n; ++i) {
pw[i] = 2 * pw[i - 1] % mod;
scanf("%lld", &k);
int S = 0;
for (int j = 1, x; j <= k; ++j)
scanf("%lld", &x), S |= 1 << (x - 1);
++f[S];
}
}
void calculate() {
for (int i = 0; i ^ m; ++i)
for (int S = 0; S ^ (1 << m); ++S) if (S >> i & 1)
f[S] = (f[S] + f[S ^ (1 << i)]) % mod;
int ans = 0;
for (int i = 0, S; i ^ (1 << m); ++i) {
S = (1 << m) - i - 1;
int c = (__builtin_popcount(i) & 1) ? -1 : 1;
ans = (ans + c * (pw[f[S]] - 1) % mod + mod) % mod;
}
cout << ans << '\n';
}
void solve() {
init();
calculate();
}
signed main() {
solve();
return 0;
}
using namespace
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步