容斥原理

容斥原理

原型

image-20211224170532703.png

求n个相交的集合的元素总个数

原理

QQ浏览器截图20200807160444.png

例如:
image-20211224170933221.png

证明

设元素x在所有集合中出现了k次,然后写出等式,应用组合数恒等式
image-20211224212016723.png

例题

给定一个整数 n 和 m 个不同的质数 p1, p2, …,pm。

请你求出 1∼n 中能被 p1, p2, …,pm 中的至少一个数整除的整数有多少个。

例子

image-20211224213053528.png

如何计算?

\(|S_{p_i}|\)\(1- n\)\(p_i\)的倍数的个数,则

\[|S_{p_i}| = \lfloor \frac{n}{p_i} \rfloor \\ |S_{p1} \cap S_{p2}\cap ...\cap S_{pn}| =\lfloor \frac{n}{p_1, p_2,...p_n的最小公倍数} \rfloor \\\overset{p_i均为质数}{=} \lfloor \frac{n}{p_1 p_2...p_n} \rfloor \]

由容斥原理有

\[|S_{p_1} \cup S_{p_2} \cup ... \cup S_{p_m}|\\ = |S_{p_1}| + |S_{p_2}| + ... + |S_{p_m}| - |S_{p_1} \cap S_{p_2}| - ... + |S_{p_1} \cap S_{p_2} \cap S_{p_3} | - ...\\ = \sum _{i}|S_i| - \sum _{i,j}|S_i \cap S_j| + \sum _{i,j,k}|S_i \cap S_j \cap S_k|-... \]

这里面一共有\(2^n - 1\) 项,对应了除了全部不取以外,每个集合取或不取的所有情况

于是我们要枚举所有的选法,常用的方法是使用位运算

//枚举,从i枚举到2^n - 1
for (int i = 1; i < 1 << n; i ++ ){
    ...
}
//判断第k位是否为1
if (i >> k & 1){
    ...
}

代码

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 20;

int p[N];

int main(){
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < m; i ++ ) cin >> p[i];

    int ans = 0;

    for (int i = 1; i < 1 << m; i ++ ){         //这里应把i看成二进制数,代表了一种选法的序列,代表了计算|S|的分母
        int pd = 1, cnt = 0;                    //pd(product)记录分母,若干个质数的乘积;cnt,有多少个质数,决定后面计算中是加是减
        for (int j = 0; j < m; j ++ ){          //j枚举i的第j位,i的每个第j位对应了某个集合要不要乘到分母里
            if (i >> j & 1){
                if ((LL)pd * p[j] > n){         //公式里不是所有的项都能计算,可能出现n小于某些数的乘积,故要特判。另外这里要小心爆int
                    pd = -1;
                    break;
                }
                pd *= p[j];                     //更新
                cnt ++ ;
            }
        }
        if (pd != -1){
            if (cnt % 2 == 0) ans -= n / pd;    //按照公式计算
            else ans += n / pd;
        }
    }

    cout << ans << endl;
    return 0;
}
posted @ 2021-12-24 22:35  tsrigo  阅读(81)  评论(0编辑  收藏  举报