容斥原理


容斥原理

基本概念

容斥原理

在计数时,必须注意没有重复,没有遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。(摘自百度百科)

举个栗子

  1. 三个集合:
    image
    三个集合\(s_{1},s_{2},s_{3}\), 目标是求解\(s_{1} \cup s_{2} \cup s_{3}\)
    易知:

\[s_{1} \cup s_{2} \cup s_{3} = (s_{1} + s_{2} + s_{3}) - (s_{1} \cap s_{2} + s_{2} \cap s_{3} + s_{1} \cap s_{3}) + (s_{1} \cap s_{2} \cap s_{3}) \]

  1. 四个集合:
    image
    四个集合:\(s_{1},s_{2},s_{3}, s_{4}\), 目标是求解:\(s_{1} \cup s_{2} \cup s_{3} \cup s_{4}\)
    那么:

\[s_{1} \cup s_{2} \cup s_{3} \cup s_{4} = (s_{1} + s_{2} + s_{3} + s_{4}) - (s_{1} \cap s_{2} + s_{2} \cap s_{3} + s_{1} \cap s_{3} + s_{1} \cap s_{4} + s_{2} \cap s_{4} + s_{3} \cap s_{4}) + (s_{1} \cap s_{2} \cap s_{3} + s_{1} \cap s_{2} \cap s_{4} + s_{4} \cap s_{2} \cap s_{3}) - (s_{1} \cap s_{2} \cap s_{3} \cap s_{4}) \]

容斥原理公式

\(\lvert A_{1} \cap A_{2} \cap A_{3} \cap ... A_{m}\lvert =\)

\[\sum_{1 \leqslant i \leqslant m}\lvert A_{i}\lvert - \sum_{1 \leqslant i < j \leqslant m}\lvert A_{i} \cap A_{j} \lvert + \sum_{1 \leqslant i < j < k \leqslant m}\lvert A_{i} \cap A_{j} \cap A_{k}\lvert - ... + (-1)^{m - 1}\lvert A_{1} \cap A_{2} \cap A_{3} \cap ... A_{m}\lvert \]

规律:

奇数项为正, 偶数项为负.

例题

AcWing 890. 能被整除的数

原题链接

题意

给定\(m\)个质数, 问\(1 - n\)中有多少个数能至少被\(m\)个数中的一个整除

时间复杂度

\(m\)个质因数(即\(m\)个集合), 总共\(2^m\)种组合方式, 每种组合方式对应二进制有\(m\)
可得时间复杂度:\(O(m\times 2^m)\)

思路

Step 1

对于\(m\)个质数(\(p_{1}, p_{2} ... p_{m}\)), 每个数都存在这样一个集合:

\[S_{1} : p_{1}:p_{1},1p_{1}, 2p_{1} ... kp_{1} \]

\[... \]

\[S_{m} : p_{m}:p_{m},1p_{m}, 2p_{m} ... kp_{m} \]

集合\(S_{i}\)代表所有能够被\(p_{i}\)整除的数.
那么根据题意, 我们目标就是求解

\[S_{1} \cup S_{2} ... S_{m - 1} \cup S_{m} \]

中元素的个数
只要应用容斥原理即可.

Step 2

我们发现:
\(\sum_{1 \leqslant i \leqslant m}\lvert A_{i}\lvert\) 是从\(i\)个集合中选择1个, 选法有\(C^{1}_{i}\)
\(\sum_{1 \leqslant i < j \leqslant m}\lvert A_{i} \cap A_{j} \lvert\) 是从\(i\)个集合内选择2个, 选法有\(C^{2}_{i}\)种.
\(...\)
那么选择容斥原理后, 还有一个问题要解决: 如何枚举公式中的每种情况?
我们采用二进制:
每个二进制数都由\(01\)组成, 那么对于每一位: \(0\)代表选择这个集合, \(1\)代表不选择这个集合
例如\(1010\):代表有四个集合:选择集合\(1,3\), 不选\(2, 4\)
只要依次枚举\(1 - 2^{m} - 1\), 就可以枚举集合的所有选法
(其实: \(C^{0}_{m} + C^{1}_{m} + C^{2}_{m} + ... + C^{m}_{m} = 2^{m}\))

Step 3

如何计算当前集合内能被\(1 - n\)整除的元素个数:

\[\lfloor \frac{n}{p_{1} p_{2} ... p_{k}} \rfloor \]

#include <iostream>
#include <cstring>

using namespace std;

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 res = 0;
    for (int i = 1; i < 1 << m; i ++ ){
        int cnt = 0;
        int t = 1;
        for (int j = 0; j < m; j ++ ){
            if (i >> j & 1){                //选择当前集合
                cnt ++;
                if ((long long)t * p[j] > n){
                    t = -1;
                    break;
                }
                t = (long long)t * p[j];
            }
        }
        if (t != -1){
            if (cnt % 2)
                res += n / t;
            else
                res -= n / t;
        }
        
    }
    
    cout << res << endl;
    
    return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM