容斥的定义
公式
对于一个 有限集 ,有
也可以写成
其中 表示 的 基数 (即集合中包含的元素的「个数」)。例如在两个集的情况时,我们可以通过将 和 相加,再减去其交集的基数,而得到其并集的基数
其他应用
若
那么就有
上面就是子集反演的形式....
例题
已知 , 求
直接上容斥即可
求把大小为 的集合分为 个非空子集的方案数 (第二类斯特林数)
考虑容斥
对于容斥原理的题目,都有一个通用的套路
- 首先将题目中的 个限制列出来
- 枚举限制的 个子集
- 对于每个子集 计算出不满足子集中条件的个数
- 若 为奇数,那么 就减去 ,否则 就加上
Min-Max 容斥
定义对于集合 的运算 是
具体证明使用二项式反演证明
例题:
TopCoder-14766 TestProctoring
现在有 个学生,每个学生在每一秒都有一个概率,表示该学生在该时刻考完试的概率。现在需要求所有学生考完试所需要的期望时间。
直接算,不会期望请参考上一篇博客
令 表示第 个学生考完试的时间,那么就转化为了求
由于期望线性性及 容斥,可以得到
所以就转化为求 了,也就是期望第一个学生考完试的期望时间
考虑 中的学生第 时刻有人考完试的概率 ,有
所以说它的期望就是
代码比较 naive,就不放了。(实际上把下面那题的代码改改就能过)
HDU4336
链接: 这里
有 个卡牌,每秒有 的概率抽到卡牌 ,求至少得到每个卡牌至少一张的期望时间
令 为获得第 张牌的时间,那么题目中就是求 ,这就是 容斥的形式,我们将它写出来:
注意到对于 相当于 第一次 抽到卡牌的时间,所以
然后代码就很简单:
Code:
#include <bits/stdc++.h> static const bool ParityTable256[256] = { #define P2(n) n, n^1, n^1, n #define P4(n) P2(n), P2(n^1), P2(n^1), P2(n) #define P6(n) P4(n), P4(n^1), P4(n^1), P4(n) P6(0), P6(1), P6(1), P6(0) }; const int MAXN = 20 + 5; double bit[MAXN]; inline double split (unsigned int S) { double ret = 0; for (unsigned int i = 0; i <= 20 && (S >= (1 << i)); i++){ if (S & (1 << i)) ret += bit[i]; // printf("[DEBUG]i = %d, i << %d = %d, bit[%d] = %d\n", i, i, 1 << i, i, bit[i]); } return ret; } int main (int argc, char *const argv[]) { int n; while (~scanf("%d", &n)) { for (int i = 0; i <= n - 1; i++) std::cin >> bit[i]; double ans = 0; for (unsigned int i = 1; i < (1 << n); i++) { // 另一种枚举子集 unsigned char *p = (unsigned char *) &i; double cnt = (ParityTable256[p[0] ^ p[1] ^ p[2] ^ p[3]]) ? 1.0 : -1.0; // 上面是判断二进制位中 1 个数的奇偶性 // 不过我可能写麻烦了 (?) // printf("[DEBUG]cnt = %lf, split(%d) = %lf\n", cnt, i, split(i)); ans += cnt / split(i); } printf("%.4lf\n", ans); } return 0; }