# 容斥定理
基本描述
对于上述图片,求
结果为
一般描述
公式:
大家应该是看不懂吧,反正我是看不懂
我理解的通俗的意思就是:
n
个集合的并集等于n
个集合选择一个的情况中所有情况的交-n
个集合中选择两个所有情况中两两的交+n
个集合中选择三个中所有情况三个的交-选择四种的交+选择五种的交-…
稍微用公式表示一下:
大概就是这个意思。
- 选择的个数为偶数次,前面符号为负
- 选择的个数为奇数次,前面符号为正
参考链接:
https://oi-wiki.org/math/combinatorics/inclusion-exclusion-principle/
例题1 能被整除的数
题目链接:https://www.acwing.com/problem/content/892/
给定一个整数
n
和m
个不同的质数 。
请你求出 1∼n 中能被 中的至少一个数整除的整数有多少个。
思路:
先简单的举个例子:
质数有2,3,5,7,11五个
能被2整除的有2,4,6,8 …
能被3整除的有3,6,9,12…
…
问的是被至少一个整除就行,那么上述例子中6是重复的
也就是我们可以把能被一个质数整除的个数当作一个集合,这么多质数组成的集合有重合的,我们要求的是这么多集合的并集,满足容斥定理
在1-n
中能被x
整除的个数:
在1-n
中能被x,y
整除的个数:
在1-n
中能被x,y,z
整除的个数:
…
然后就可以根据公式求结果,有m
个质数,共有m
个集合,每次会选中若干个集合,代表几个之间的交集(参照上面容斥定理公式)
几个集合之间的交集:就是一个数能同时被这选中的几个质数整除
选中集合个数为偶数,前面符号为负
选中集合个数为奇数,前面符号为正
枚举所有的集合:
我们采用二进制的方法,枚举,统计其中1
的个数即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 20;
int p[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++) cin>>p[i];
ll res = 0;
for(int i=1;i< 1<<m ;i++)
{
int cnt = 0;
ll t = 1;
for(int j=0;j<m;j++)
{
if( i>>j & 1)
{
cnt ++ ;//统计选中的个数
if(t * p[j] > n)//不满足条件,因为大于n了
{
t = -1;
break;
}
t *= p[j];
}
}
if(t!=-1)
{
if(cnt%2) res += n/t;
else res -= n/t;
}
}
cout << res << endl;
return 0;
}
例题2
题目链接:https://www.acwing.com/problem/content/216/
Devu 有
N
个盒子,第i
个盒子中有 枝花。同一个盒子内的花颜色相同,不同盒子内的花颜色不同。Devu 要从这些盒子中选出m
枝花组成一束,求共有多少种方案。若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。结果需对 取模之后方可输出
思路
- 理想情况
首先去掉限制考虑理想情况,即每个盒子的花的个数有无限个,设第个盒子取出朵花
则,总方案数为种
可以参考链接查看如何计算 :
https://oi-wiki.org/math/combinatorics/inclusion-exclusion-principle/
- 有限制的情况
限制条件下:
同时满足限制条件才可,我们考虑从补集去求(总情况数减去相反的情况)
补集情况下:
第i
个限制()的情况满足个数我们当作一个集合
- 总数:
- 题目的补集:
- 结果为:
考虑求满足的情况数
即第一个必须取至少支花,那么接下来就化为从n
组里面选花,的情况,可以直接取出支花放在第一组,那么总数就变成支花,就化为理想情况(见上文)下的问题了,总数为
同理
答案为
最后枚举所有的情况数,使用二进制方法进行枚举,枚举,统计二进制位1
的个数
奇数个1
符号为负
偶数个1
符号位正
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 20,mod = 1e9+7;
ll a[N];
ll d = 1;
ll fast(ll a,ll b)
{
ll res = 1;
while(b)
{
if(b&1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll C(ll x,ll y)
{
if(x < y) return 0;
ll u = 1;
for(ll i = x;i>x-y;i--) u = i % mod * u % mod;
return u * d % mod;
}
int main()
{
ll n,m;
cin >> n >> m;
for ( int i=0;i<n;i++) cin>>a[i];
for(ll i=1;i<=n-1;i++) d = d * i % mod;
d = fast(d,mod-2);
ll res = 0 ;
for(int i=0; i< 1<<n;i++)
{
//组合数的下角标 上角标
ll down = n + m -1,up = n-1;
int sign = 1;//标记的符号位
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
down -= a[j]+1;//下角标减去对应的个数
sign *= -1;
}
}
res = (res + C(down,up) * sign)%mod; //计算组合数,统计答案
}
cout<<(res + mod) % mod<<endl;
return 0;
}
例题3 ABC 297 F Minimum Bounding Box 2
AtCoder Beginner Contest 297 F Minimum Bounding Box 2
枚举矩形的长h和宽w,长h宽w的矩形的情况数总共有 个,每个矩形框对应的分数为
容斥时,共有四个变量,对应4个二进制位, 对应上下左右四个方向是否收缩1个单位,收缩的总数为奇数时,符号为负,为偶数时,符号为正。
注意容斥变量s是从0开始,0刚好对应总的情况数,所以不用额外计算总的情况数
最后再除以总的情况数 即可
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 998244353;
const int N = 1e6;
ll ksm(ll a, ll b) {
ll res = 1;
a %= mod;
while(b) {
if(b & 1) res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int h, w, k;
cin >> h >> w >> k;
vector<ll> fac(N + 1), inv(N + 1);
fac[0] = 1;
for(int i = 1; i <= N; i++) {
fac[i] = fac[i - 1] * i % mod;
}
inv[N] = ksm(fac[N], mod - 2);
inv[0] = 1;
for(int i = N - 1; i >= 1; i--) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
auto C = [&](ll n, ll m) -> ll {
if(n < m || m < 0) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
};
ll ans = 0;
for(int i = 1; i <= h; i++) {
for(int j = 1; j <= w; j++) {
ll res = 0;
for(int s = 0; s < 16; s++) {
int u = __builtin_popcount(s & 3);
int v = __builtin_popcount(s & 12);
res += ((u + v) & 1 ? -1 : 1) * C(max(0, i - u) * max(0, j - v), k) % mod;
res %= mod;
}
ans += res * (h - i + 1) % mod * (w - j + 1) % mod * i % mod * j % mod;
ans %= mod;
}
}
ans *= ksm(C(h * w, k), mod - 2);
ans %= mod;
cout << (ans + mod) % mod << "\n";
return 0;
}