🐋
Blog
算法
算法
  • 算法
  • 基础
    • 高精度
    • 二分
    • 位运算
    • 贪心
    • KMP
    • Master Theorem
    • 前缀和 & 差分
    • sort
    • 双指
  • 数据结构
    • 数据结构模拟
  • 数学
    • 组合数
    • 约数
    • 欧拉函数
    • 扩展欧几里得
    • 高斯消元
    • 容斥原理
    • 线性筛
    • 快速幂
  • 动态规划
    • 背包
    • 字符串匹配
    • 区间DP
  • 图论
    • BFS
    • DFS
由 GitBook 提供支持
在本页
  1. 数学

容斥原理

上一页高斯消元下一页线性筛

最后更新于2年前

容斥原理

容斥原理就是求几个集合的并集的元素个数的公式。换句话说就是整体韦恩图的元素个数。

∣⋃i=1nAi∣=∑i=1n∣Ai∣−∑1≤i<j≤n∣Ai∩Aj∣+∑1≤i<j<k≤n∣Ai∩Aj∩Ak∣−⋯+(−1)n−1∣A1∩⋯∩An∣|\bigcup_{i=1}^n A_i| = {\sum_{i=1}^n |A_i|} - {\sum_{1 \le i \lt j \le n} |A_i \cap A_j|} + {\sum_{1 \le i \lt j \lt k \le n} |A_i \cap A_j \cap A_k|} - \cdots + (-1)^{n-1}|A_1 \cap \cdots \cap A_n|∣i=1⋃n​Ai​∣=i=1∑n​∣Ai​∣−1≤i<j≤n∑​∣Ai​∩Aj​∣+1≤i<j<k≤n∑​∣Ai​∩Aj​∩Ak​∣−⋯+(−1)n−1∣A1​∩⋯∩An​∣

其中 ∣A∣|A|∣A∣ 表示 AAA 的基数。例如在两个集合的情况,我们可以通过将 ∣A∣|A|∣A∣ 和 ∣B∣|B|∣B∣ 相加,再减去其交际的基数,而得到其并集的基数。

三个集合的容斥原理

∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣|A \cup B \cup C| = |A| + |B| + |C| - |A \cap B| - |A \cap C| - |B \cap C| + |A \cap B \cap C|∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣

注意公式中符号,奇数为加,偶数为减。还有总体数量为 (31)3 \choose 1(13​) + (32)3 \choose 2(23​) + (33)3 \choose 3(33​),我们知道组合数的全加与二项式的系数有关,公式为:

(1+x)n=∑k=0n(nk)xk=(n0)+(n1)x+⋯+(nn)xn(1 + x)^n = \sum_{k=0}^n{n \choose k}x^k = {n \choose 0} + {n \choose 1}x + \cdots + {n \choose n}x^n(1+x)n=k=0∑n​(kn​)xk=(0n​)+(1n​)x+⋯+(nn​)xn

令 x=1x = 1x=1,则上式为:

2n=∑k=0n(nk)=(n0)+(n1)+⋯+(nn)2n−1=(n1)+⋯+(nn)2^n = \sum_{k=0}^n{n \choose k} = {n \choose 0} + {n \choose 1} +\cdots + {n \choose n} \\ 2^n - 1 = {n \choose 1} + \cdots + {n \choose n}2n=k=0∑n​(kn​)=(0n​)+(1n​)+⋯+(nn​)2n−1=(1n​)+⋯+(nn​)

可以发现我们相求的算数的总体数量为 2n−12^n - 12n−1。 经过上述分析,我们可以通过利用枚举方式从 0∼2n−10 \sim 2^n - 10∼2n−1 枚举所有项来

能被整除的数

给定一个整数 nnn 和 mmm 个不同的质数 p1,p2,…,pmp_1, p_2, \dots, p_mp1​,p2​,…,pm​ 。请你求出 1∼n1 ∼ n1∼n 中能被 p1,p2,…,pmp_1, p_2, \dots, p_mp1​,p2​,…,pm​ 中的至少一个数整除的整数有多少个。

分析题意是求所有的集合交集的基数,可利用容斥原理。

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;

const int M = 20;
int p[M];

int main(){
    int n, m; cin >> n >> m;
    for (int i = 0; i < m; i ++) cin >> p[i];
    int res = 0;
    for (int i = 1; i < 1 << m; i ++){ // 所有项 二进制上的位来判断选了那一项
        int t = 1, cnt = 0;    // t 是乘积,表示 p_x * p_y、cnt 是计数选了多少个集合,便于判断符号 
        for (int j = 0; j < m; j ++){    // 通过判断所有位来计算到底选了那一位
            if (i >> j & 1){
               if ((LL)t * p[j] > n){   // 乘过头了
                   t = -1;
                   break;
               }
               t *= p[j];
               cnt ++;
            }
        }
        if (t != -1){    
            if (cnt % 2) res += n / t;
            else res -= n / t;
        }
    }
    cout << res << endl;
}

时间复杂度

两层 for 循环可得时间复杂度 $$O(m * 2^m)$$

给出 nnn 个字符串,每个长度为 222,包含 'a' ~ 'k' 的字符,比较每两个字符串,输出只有一个下标相同且其字符相同的 pair 数量。

这题也是用容斥原理在解决的。第一个下标相同且字符相同的数量加上第二个下标相同且字符相同的数量减去两倍的两个下标都相同且字符相同的数量。 如何计算下标相同且字符相同的数量? 针对其下标,共有多少的字符相同的数量,然后计算其以 222 组合的数量。

2-Letter Strings