容斥原理

容斥原理

概念

这是一道小学数学题

在一个 \(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\) 的集合相交)。只需要枚举一个正整数就可以了

例题

洛谷 P1450 [HAOI2008] 硬币购物

这道题本质上是一个多重背包,但每一次都会重新计算,会超时

但可以发现,只有背包体积和物品数量被改变,物品的体积没有变化

考虑用容斥原理表示

背包体积为 \(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;
} 
posted @ 2025-05-31 13:47  nightmare_lhh  阅读(19)  评论(0)    收藏  举报