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
#include "iostream" using namespace std; constexpr int N = 1e6 + 10, M = 20, mod = 1e9 + 7; #define int long long 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; }
posted @   Steven1013  阅读(4)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开