容斥原理


容斥原理

基本概念

容斥原理

在計數時,必須注意沒有重復,沒有遺漏。為了使重疊部分不被重復計算,人們研究出一種新的計數方法,這種方法的基本思想是:先不考慮重疊的情況,把包含於某內容中的所有對象的數目先計算出來,然后再把計數時重復計算的數目排斥出去,使得計算的結果既無遺漏又無重復,這種計數的方法稱為容斥原理。(摘自百度百科)

舉個栗子

  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