容斥原理
容斥原理
概念
这是一道小学数学题:
在一个 \(40\) 人的班中,所有人都有擅长的学科,\(15\) 个人擅长英语,\(20\) 个人擅长数学,\(10\) 个人擅长语文,问有几个人三科都擅长
可以画出韦恩图:

此时,\(A\) 代表语文,\(B\) 代表数学,\(C\) 代表英语
则题目变为已知 \(|A|\),\(|B|\),\(|C|\),求 \(|A\cap B\cap C|\)
很容易得出答案:
\[|A\cup B\cup C| = |A|+|B|+|C|-|A\cap B|-|A\cap C|-|B\cap B|+|A\cap B\cap C|
\]
推广可得:
\[|A_1\cup A_2\cup\cdots\cup A_n|=\sum^{n}_{i=1}|A_i|-\sum^{n-1}_{i=1}\sum^{n}_{j=i+1}|A_i\cap A_j|+\sum^{n-2}_{i=1}\sum^{n-1}_{j=i+1}\sum^{n}_{k=j+1}|A_i\cap A_j\cap A_k|-\cdots+(-1)^{n-1}|A_1\cap A_2 \cap\cdots\cap A_n|
\]
证明:
假设一个元素 \(x\) ,被若干个集合(\(A_1,A_2,\cdots,A_n\))包含,则:
- 在左侧被计数一次
- 在右侧被计数了 \(C_k^1-C^2_k+\cdots+(-1)^{k+1}C_k^k=(-1)\times(1-1)^k+1=1\) 次
技巧
容斥原理与状态压缩
对于枚举若干个集合的交集,可以使用二进制串表示(为 \(1\) 的集合相交)。只需要枚举一个正整数就可以了
例题
这道题本质上是一个多重背包,但每一次都会重新计算,会超时
但可以发现,只有背包体积和物品数量被改变,物品的体积没有变化
考虑用容斥原理表示:
背包体积为 \(s\) 的正确答案 \(=\) 无限物体数量的体积为 \(s\) 的背包 \(-\) 超出数量的物体的答案贡献
可以让每一个物体都强制超出限制,即达到 \((d_i+1)\),它的答案贡献为 \(c_i(d_i+1)\)
根据容斥原理即可求出答案:
#include <bits/stdc++.h>
#define ll long long
const int maxn = 1e5;
int n;
ll c[5],d[4],s;
ll dp[maxn + 5];
int main() {
scanf("%lld %lld %lld %lld %d",&c[1],&c[2],&c[3],&c[4],&n);
dp[0] = 1ll;
for (int i = 1;i <= 4;i ++)
for (int j = c[i];j <= maxn;j ++)
dp[j] += dp[j - c[i]];
for (int i = 1;i <= n;i ++) {
scanf("%lld %lld %lld %lld %lld",&d[1],&d[2],&d[3],&d[4],&s);
ll res = dp[s];
for (int j = 1;j <= 15;j ++) {
ll cnt = 0,ans = 0;
for (int k = 0;k < 4;k ++) { // 状态压缩
if (j & (1 << k)) {
cnt ++;
ans += (d[k + 1] + 1) * c[k + 1]; // 统计贡献
}
}
if (ans > s) continue;
res -= ((cnt % 2 == 0) ? -1 : 1) * dp[s - ans]; // 容斥
}
printf("%lld\n",res);
}
return 0;
}

浙公网安备 33010602011771号