容斥的定义

公式

对于一个 有限集 ,有

也可以写成

其中 表示 基数 (即集合中包含的元素的「个数」)。例如在两个集的情况时,我们可以通过将 相加,再减去其交集的基数,而得到其并集的基数

其他应用

那么就有

上面就是子集反演的形式....

例题

已知 , 求

直接上容斥即可

求把大小为 的集合分为 个非空子集的方案数 (第二类斯特林数)

考虑容斥


对于容斥原理的题目,都有一个通用的套路

  1. 首先将题目中的 个限制列出来
  2. 枚举限制的 个子集
  3. 对于每个子集 计算出不满足子集中条件的个数
  4. 为奇数,那么 就减去 ,否则 就加上

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;
}