容斥原理CSP-S

目录

💡 核心思想

容斥原理是计算"至少满足一个条件"或"不满足任何条件"的元素个数的工具。对于两个集合:|A∪B| = |A| + |B| - |A∩B|。对于 n 个集合,公式为:|∪Aᵢ| = Σ|Aᵢ| - Σ|Aᵢ∩Aⱼ| + Σ|Aᵢ∩Aⱼ∩Aₖ| - ... + (-1)^{n+1}|∩Aᵢ|。在竞赛中,容斥常用于:计数问题、概率问题、莫比乌斯反演。

容斥原理是"正面不好算,就算反面"思想的数学化。很多计数题直接算非常复杂,但用容斥把问题拆成几个简单子问题的加减组合,瞬间变得可做。关键技巧:枚举所有子集(1<

🎯 直觉理解

想象你在数班里的同学会打篮球或会游泳的人数。直接数"会打篮球或会游泳"很难,因为有人两项都会。容斥原理说:先数会篮球的,加会游泳的,但两项都会的人被数了两次,所以要减掉一次。这就是容斥的核心:多算了就减,少算了就加。

📝 算法流程

  1. 确定 n 个条件/集合
  2. 枚举所有非空子集(bitmask 从 1 到 (1<
  3. 对于每个子集 S:
  4. 计算满足 S 中所有条件的元素个数 cnt(S)
  5. 如果 |S| 为奇数,加上 cnt(S);为偶数,减去 cnt(S)
  6. 最终结果为总元素数减去上述容斥结果(或直接用容斥公式)

$$|A_1 \cup A_2 \cup \dots \cup A_n| = \sum_{i} |A_i| - \sum_{i

📊 复杂度分析

指标复杂度
时间$O(2^n \cdot f(n))$(f(n) 为计算单个交集的时间)
空间$O(1)$

💻 参考实现(C++)

C++ (C++17)
#include 
using namespace std;

// 容斥原理:计算 1~n 中不被任何 p[i] 整除的数的个数
int inclusionExclusion(int n, vector& p) {
    int m = p.size();
    int res = 0;
    for (int mask = 1; mask < (1 << m); mask++) {
        long long lcm = 1;
        int bits = 0;
        for (int i = 0; i < m; i++) {
            if (mask >> i & 1) {
                bits++;
                lcm = lcm / gcd(lcm, (long long)p[i]) * p[i];
                if (lcm > n) break;
            }
        }
        if (lcm > n) continue;
        int cnt = n / lcm;
        if (bits % 2 == 1) res += cnt;
        else res -= cnt;
    }
    return n - res; // 不被任何 p[i] 整除
}

long long gcd(long long a, long long b) { return b == 0 ? a : gcd(b, a % b); }

int main() {
    vector p = {2, 3, 5};
    cout << inclusionExclusion(100, p) << endl; // 100 以内不被 2,3,5 整除的数
    return 0;
}

⚠️ 常见坑点

子集枚举遗漏——从 1 开始,不是从 0

符号判断错误——奇数大小子集加,偶数大小子集减

LCM 溢出——中间结果可能超过 int 范围

重复计数——确保交集的计算是正确的

📚 相关题目

题目来源难度备注
P1450 硬币购物洛谷CSP-S容斥原理计数
P2567 幸运数字洛谷CSP-S容斥 + DFS
P5221 /product洛谷CSP-S容斥原理 + 数论